0ad/source/simulation/Entity.cpp
Matei 7926b3d93c # Some groundwork for territories.
Entities with traits.is_territory_centre == true will act as territory
centres, and territory areas are calculated and displayed on the
minimap. It remains to display these areas in the game view and to make
Civ Centres "socketable" so you can build them on Settlements to claim
them.

This was SVN commit r4070.
2006-07-08 22:40:01 +00:00

2212 lines
65 KiB
C++

// Last modified: May 15 2004, Mark Thompson (mark@wildfiregames.com)
#include "precompiled.h"
#include "ps/Profile.h"
#include "Entity.h"
#include "EntityManager.h"
#include "BaseEntityCollection.h"
#include "graphics/Unit.h"
#include "Aura.h"
#include "ProductionQueue.h"
#include "renderer/Renderer.h"
#include "graphics/Model.h"
#include "graphics/Terrain.h"
#include "ps/Interact.h"
#include "Collision.h"
#include "PathfindEngine.h"
#include "ps/Game.h"
#include "maths/scripting/JSInterface_Vector3D.h"
#include "maths/MathUtil.h"
#include "ps/CConsole.h"
#include "renderer/WaterManager.h"
#include "EntityFormation.h"
#include "FormationManager.h"
#include "TerritoryManager.h"
#include "Formation.h"
#include "graphics/GameView.h"
#include "graphics/Sprite.h"
#include "graphics/UnitManager.h"
extern CConsole* g_Console;
extern int g_xres, g_yres;
#include <algorithm>
using namespace std;
std::map<CStr, size_t> CEntity::m_AttributeTable;
CEntity::CEntity( CBaseEntity* base, CVector3D position, float orientation, const std::set<CStrW>& actorSelections, CStrW building )
{
if(m_AttributeTable.size() == 0) // not nice
{
initAttributes(this);
}
m_position = position;
m_orientation.Y = orientation;
m_ahead.x = sin( m_orientation.Y );
m_ahead.y = cos( m_orientation.Y );
m_player = 0;
/* Anything added to this list MUST be added to BaseEntity.cpp (and variables used should
also be added to BaseEntity.h */
AddProperty( L"actions.move.speed_curr", &m_speed );
AddProperty( L"actions.move.run.speed", &m_runSpeed );
AddProperty( L"actions.move.run.rangemin", &( m_run.m_MinRange ) );
AddProperty( L"actions.move.run.range", &( m_run.m_MaxRange ) );
AddProperty( L"actions.move.run.regen_rate", &m_runRegenRate );
AddProperty( L"actions.move.run.decay_rate", &m_runDecayRate );
AddProperty( L"actions.move.pass_through_allies", &m_passThroughAllies );
AddProperty( L"selected", &m_selected, false, (NotifyFn)&CEntity::checkSelection );
AddProperty( L"group", &m_grouped, false, (NotifyFn)&CEntity::checkGroup );
AddProperty( L"traits.extant", &m_extant );
AddProperty( L"actions.move.turningradius", &m_turningRadius );
AddProperty( L"position", &m_graphics_position, false, (NotifyFn)&CEntity::teleport );
AddProperty( L"orientation", &(m_orientation.Y), false, (NotifyFn)&CEntity::reorient );
AddProperty( L"player", &m_player, false, (NotifyFn)&CEntity::playerChanged );
AddProperty( L"traits.health.curr", &m_healthCurr );
AddProperty( L"traits.health.max", &m_healthMax );
AddProperty( L"traits.health.bar_height", &m_healthBarHeight );
AddProperty( L"traits.health.bar_size", &m_healthBarSize );
AddProperty( L"traits.health.bar_width", &m_healthBarWidth );
AddProperty( L"traits.health.border_height", &m_healthBorderHeight);
AddProperty( L"traits.health.border_width", &m_healthBorderWidth );
AddProperty( L"traits.health.border_name", &m_healthBorderName );
AddProperty( L"traits.health.regen_rate", &m_healthRegenRate );
AddProperty( L"traits.health.regen_start", &m_healthRegenStart );
AddProperty( L"traits.health.decay_rate", &m_healthDecayRate );
AddProperty( L"traits.stamina.curr", &m_staminaCurr );
AddProperty( L"traits.stamina.max", &m_staminaMax );
AddProperty( L"traits.stamina.bar_height", &m_staminaBarHeight );
AddProperty( L"traits.stamina.bar_size", &m_staminaBarSize );
AddProperty( L"traits.stamina.bar_width", &m_staminaBarWidth );
AddProperty( L"traits.stamina.border_height", &m_staminaBorderHeight);
AddProperty( L"traits.stamina.border_width", &m_staminaBorderWidth );
AddProperty( L"traits.stamina.border_name", &m_staminaBorderName );
AddProperty( L"traits.rally.name", &m_rallyTexture );
AddProperty( L"traits.rally.width", &m_rallyWidth );
AddProperty( L"traits.rally.height", &m_rallyHeight );
AddProperty( L"traits.flank_penalty.sectors", &m_sectorDivs);
AddProperty( L"traits.pitch.sectors", &m_pitchDivs );
AddProperty( L"traits.rank.width", &m_rankWidth );
AddProperty( L"traits.rank.height", &m_rankHeight );
AddProperty( L"traits.rank.name", &m_rankName );
AddProperty( L"traits.minimap.type", &m_minimapType );
AddProperty( L"traits.minimap.red", &m_minimapR );
AddProperty( L"traits.minimap.green", &m_minimapG );
AddProperty( L"traits.minimap.blue", &m_minimapB );
AddProperty( L"traits.anchor.type", &m_anchorType );
AddProperty( L"traits.anchor.conformx", &m_anchorConformX );
AddProperty( L"traits.anchor.conformz", &m_anchorConformZ );
AddProperty( L"traits.vision.los", &m_los );
AddProperty( L"traits.vision.permanent", &m_permanent );
AddProperty( L"traits.is_territory_centre", &m_isTerritoryCentre );
AddProperty( L"last_combat_time", &m_lastCombatTime );
AddProperty( L"last_run_time", &m_lastRunTime );
AddProperty( L"building", &m_building );
m_productionQueue = new CProductionQueue( this );
AddProperty( L"production_queue", m_productionQueue );
for( int t = 0; t < EVENT_LAST; t++ )
{
AddProperty( EventNames[t], &m_EventHandlers[t], false );
AddHandler( t, &m_EventHandlers[t] );
}
m_collisionPatch = NULL;
// Set our parent unit and build us an actor.
m_actor = NULL;
m_bounds = NULL;
m_lastState = -1;
m_transition = true;
m_fsm_cyclepos = NOT_IN_CYCLE;
m_base = base;
m_actorSelections = actorSelections;
loadBase();
m_sectorValues.resize(m_sectorDivs);
for ( int i=0; i<m_sectorDivs; ++i )
m_sectorValues[i] = false;
if( m_bounds )
m_bounds->setPosition( m_position.X, m_position.Z );
m_position_previous = m_position;
m_orientation_previous = m_orientation;
m_graphics_position = m_position;
m_graphics_orientation = m_orientation;
m_actor_transform_valid = false;
m_hasRallyPoint = false;
m_destroyed = false;
m_selected = false;
m_isRunning = false;
m_shouldRun = false;
m_triggerRun = false;
m_healthDecay = false;
m_frameCheck = 0;
m_lastCombatTime = 0;
m_lastRunTime = 0;
m_currentNotification = 0;
m_currentRequest = 0;
m_destroyNotifiers = true;
m_formationSlot = -1;
m_formation = -1;
m_grouped = -1;
m_building = building;
m_associatedTerritory = 0;
m_player = g_Game->GetPlayer( 0 );
}
CEntity::~CEntity()
{
if( m_actor )
{
g_UnitMan.RemoveUnit( m_actor );
delete( m_actor );
}
if( m_bounds )
{
delete( m_bounds );
}
delete m_productionQueue;
for( AuraTable::iterator it = m_auras.begin(); it != m_auras.end(); it++ )
{
delete it->second;
}
m_auras.clear();
m_destroyNotifiers=true;
for ( size_t i=0; i<m_listeners.size(); i++ )
m_listeners[i].m_sender->DestroyNotifier( this );
DestroyAllNotifiers();
CEntity* remove = this;
g_FormationManager.RemoveUnit(remove);
}
void CEntity::loadBase()
{
if( m_actor )
{
g_UnitMan.RemoveUnit( m_actor );
delete( m_actor );
m_actor = NULL;
}
if( m_bounds )
{
delete( m_bounds );
m_bounds = NULL;
}
CStr actorName ( m_base->m_actorName ); // convert CStrW->CStr8
m_actor = g_UnitMan.CreateUnit( actorName, this, m_actorSelections );
// Set up our instance data
SetBase( m_base );
m_classes.SetParent( &( m_base->m_classes ) );
SetNextObject( m_base );
if( m_base->m_bound_type == CBoundingObject::BOUND_CIRCLE )
{
m_bounds = new CBoundingCircle( m_position.X, m_position.Z, m_base->m_bound_circle );
}
else if( m_base->m_bound_type == CBoundingObject::BOUND_OABB )
{
m_bounds = new CBoundingBox( m_position.X, m_position.Z, m_ahead, m_base->m_bound_box );
}
m_actor_transform_valid = false;
if( m_player )
{
// Make sure the actor has the right player colour
m_actor->SetPlayerID( m_player->GetPlayerID() );
}
}
void CEntity::initAttributes(const CEntity* _this)
{
#define getoffset(member) \
(size_t) ((unsigned char*)&_this->member - (unsigned char*)_this)
#define getoffset_action(member, mem2) \
(size_t) ((unsigned char*)&_this->member.mem2 - (unsigned char*)_this)
//Add the attribute name and the variable that holds it
CEntity::m_AttributeTable["actions.move.speed_curr"] = getoffset(m_speed);
CEntity::m_AttributeTable["actions.move.run.speed"] = getoffset(m_runSpeed);
CEntity::m_AttributeTable["actions.move.run.rangemin"] = getoffset_action(m_run, m_MinRange);
CEntity::m_AttributeTable["actions.move.run.range"] = getoffset_action(m_run, m_MaxRange);
CEntity::m_AttributeTable["actions.move.run.regen_rate"] = getoffset(m_runRegenRate);
CEntity::m_AttributeTable["actions.move.run.decay_rate"] = getoffset(m_runDecayRate);
CEntity::m_AttributeTable["actions.move.pass_through_allies"] = getoffset(m_passThroughAllies);
CEntity::m_AttributeTable["traits.extant"] = getoffset(m_extant);
CEntity::m_AttributeTable["actions.move.turningradius"] = getoffset(m_turningRadius);
CEntity::m_AttributeTable["traits.health.curr"] = getoffset(m_healthCurr);
CEntity::m_AttributeTable["traits.health.max"] = getoffset(m_healthMax);
CEntity::m_AttributeTable["traits.health.regen_rate"] = getoffset(m_healthRegenRate);
CEntity::m_AttributeTable["traits.health.regen_start"] = getoffset(m_healthRegenStart);
CEntity::m_AttributeTable["traits.health.decay_rate"] = getoffset(m_healthDecayRate);
//This are not changable from techs until the updated bars are finished
/* CEntity::m_AttributeTable["traits.stamina.curr"] = getoffset(m_staminaCurr);
CEntity::m_AttributeTable["traits.stamina.max"] = getoffset(m_staminaMax);
CEntity::m_AttributeTable["traits.bars.height"] = getoffset(m_barHeight);
CEntity::m_AttributeTable["traits.bars"] = getoffset(m_barOffset);
CEntity::m_AttributeTable["traits.bars.width"] = getoffset(m_barWidth);
CEntity::m_AttributeTable["traits.bars.border_height"] = getoffset(m_barBorderHeight);
CEntity::m_AttributeTable["traits.bars.border_width"] = getoffset(m_barBorderWidth);
CEntity::m_AttributeTable["traits.bars.border_name"] = getoffset(m_barBorderName);*/
CEntity::m_AttributeTable["traits.flank_penalty.sectors"] = getoffset(m_sectorDivs);
CEntity::m_AttributeTable["traits.pitch.sectors"] = getoffset(m_pitchDivs);
CEntity::m_AttributeTable["traits.rank.width"] = getoffset(m_rankWidth);
// CEntity::m_AttributeTable["traits.rank"] = getoffset(m_rankOffset);
CEntity::m_AttributeTable["traits.rank.height"] = getoffset(m_rankHeight);
CEntity::m_AttributeTable["traits.rank.name"] = getoffset(m_rankName);
CEntity::m_AttributeTable["traits.minimap.type"] = getoffset(m_minimapType);
CEntity::m_AttributeTable["traits.minimap.red"] = getoffset(m_minimapR);
CEntity::m_AttributeTable["traits.minimap.green"] = getoffset(m_minimapG);
CEntity::m_AttributeTable["traits.minimap.blue"] = getoffset(m_minimapB);
CEntity::m_AttributeTable["traits.anchor.type"] = getoffset(m_anchorType);
CEntity::m_AttributeTable["traits.anchor.conformx"] = getoffset(m_anchorConformX);
CEntity::m_AttributeTable["traits.anchor.conformz"] = getoffset(m_anchorConformZ);
CEntity::m_AttributeTable["traits.vision.los"] = getoffset(m_los);
CEntity::m_AttributeTable["traits.vision.permanent"] = getoffset(m_permanent);
CEntity::m_AttributeTable["last_combat_time"] = getoffset(m_lastCombatTime);
CEntity::m_AttributeTable["last_run_time"] = getoffset(m_lastRunTime);
CEntity::m_AttributeTable["building"] = getoffset(m_building);
#undef getoffset
}
void CEntity::kill()
{
g_Selection.removeAll( me );
CEntity* remove = this;
g_FormationManager.RemoveUnit(remove);
m_destroyNotifiers=true;
for ( size_t i=0; i<m_listeners.size(); i++ )
m_listeners[i].m_sender->DestroyNotifier( this );
DestroyAllNotifiers();
if( m_bounds )
delete( m_bounds );
m_bounds = NULL;
m_extant = false;
m_destroyed = true;
//Shutdown(); // PT: tentatively removed - this seems to be called by ~CJSComplex, and we don't want to do it twice
if( m_actor )
{
g_UnitMan.RemoveUnit( m_actor );
delete( m_actor );
m_actor = NULL;
}
updateCollisionPatch();
me = HEntity(); // will deallocate the entity, assuming nobody else has a reference to it
}
void CEntity::SetPlayer(CPlayer *pPlayer)
{
m_player = pPlayer;
// Store the ID of the player in the associated model
if( m_actor )
m_actor->SetPlayerID( m_player->GetPlayerID() );
}
void CEntity::updateActorTransforms()
{
CMatrix3D m;
CMatrix3D mXZ;
float Cos = cosf( m_graphics_orientation.Y );
float Sin = sinf( m_graphics_orientation.Y );
m._11=-Cos; m._12=0.0f; m._13=-Sin; m._14=0.0f;
m._21=0.0f; m._22=1.0f; m._23=0.0f; m._24=0.0f;
m._31=Sin; m._32=0.0f; m._33=-Cos; m._34=0.0f;
m._41=0.0f; m._42=0.0f; m._43=0.0f; m._44=1.0f;
mXZ.SetXRotation( m_graphics_orientation.X );
mXZ.RotateZ( m_graphics_orientation.Z );
mXZ = m*mXZ;
mXZ.Translate(m_graphics_position);
if( m_actor )
m_actor->GetModel()->SetTransform( mXZ );
}
void CEntity::snapToGround()
{
m_graphics_position.Y = getAnchorLevel( m_graphics_position.X, m_graphics_position.Z );
}
jsval CEntity::getClassSet()
{
STL_HASH_SET<CStrW, CStrW_hash_compare>::iterator it;
it = m_classes.m_Set.begin();
CStrW result = L"";
if( it != m_classes.m_Set.end() )
{
result = *( it++ );
for( ; it != m_classes.m_Set.end(); it++ )
result += L" " + *it;
}
return( ToJSVal( result ) );
}
void CEntity::setClassSet( jsval value )
{
// Get the set that was passed in.
CStr temp = ToPrimitive<CStrW>( value );
CStr entry;
m_classes.m_Added.clear();
m_classes.m_Removed.clear();
while( true )
{
long brk_sp = temp.Find( ' ' );
long brk_cm = temp.Find( ',' );
long brk = ( brk_sp == -1 ) ? brk_cm : ( brk_cm == -1 ) ? brk_sp : ( brk_sp < brk_cm ) ? brk_sp : brk_cm;
if( brk == -1 )
{
entry = temp;
}
else
{
entry = temp.GetSubstring( 0, brk );
temp = temp.GetSubstring( brk + 1, temp.Length() );
}
if( brk != 0 )
{
if( entry[0] == '-' )
{
entry = entry.GetSubstring( 1, entry.Length() );
if( entry.Length() )
m_classes.m_Removed.push_back( entry );
}
else
{
if( entry[0] == '+' )
entry = entry.GetSubstring( 1, entry.Length() );
if( entry.Length() )
m_classes.m_Added.push_back( entry );
}
}
if( brk == -1 )
break;
}
rebuildClassSet();
}
void CEntity::rebuildClassSet()
{
m_classes.Rebuild();
InheritorsList::iterator it;
for( it = m_Inheritors.begin(); it != m_Inheritors.end(); it++ )
(*it)->rebuildClassSet();
}
void CEntity::update( size_t timestep )
{
m_position_previous = m_position;
m_orientation_previous = m_orientation;
CalculateRun( timestep );
CalculateHealth( timestep );
if ( m_triggerRun )
m_frameCheck++;
if ( m_frameCheck != 0 )
{
m_shouldRun = true;
m_triggerRun = false;
m_frameCheck = 0;
}
m_productionQueue->Update( timestep );
// Note: aura processing is done before state processing because the state
// processing code is filled with all kinds of returns
PROFILE_START( "aura processing" );
for( AuraTable::iterator it = m_auras.begin(); it != m_auras.end(); it++ )
{
it->second->Update( timestep );
}
PROFILE_END( "aura processing" );
// The process[...] functions return 'true' if the order at the top of the stack
// still needs to be (re-)evaluated; else 'false' to terminate the processing of
// this entity in this timestep.
PROFILE_START( "state processing" );
if( m_isRunning )
{
m_lastRunTime = g_Game->GetTime();
}
while( !m_orderQueue.empty() )
{
CEntityOrder* current = &m_orderQueue.front();
if( current->m_type != m_lastState )
{
m_transition = true;
m_fsm_cyclepos = NOT_IN_CYCLE;
PROFILE( "state transition / order" );
CEntity* target = NULL;
if( current->m_data[0].entity )
target = &( *( current->m_data[0].entity ) );
CVector3D worldPosition = (CVector3D)current->m_data[0].location;
CEventOrderTransition evt( m_lastState, current->m_type, target, worldPosition );
if( !DispatchEvent( &evt ) )
{
m_orderQueue.pop_front();
continue;
}
else if( target )
{
current->m_data[0].location = worldPosition;
current->m_data[0].entity = target->me;
}
m_lastState = current->m_type;
}
else
{
m_transition = false;
}
switch( current->m_type )
{
case CEntityOrder::ORDER_GOTO_NOPATHING:
case CEntityOrder::ORDER_GOTO_COLLISION:
case CEntityOrder::ORDER_GOTO_SMOOTHED:
if( processGotoNoPathing( current, timestep ) )
break;
updateCollisionPatch();
return;
case CEntityOrder::ORDER_GENERIC:
if( processGeneric( current, timestep ) )
break;
updateCollisionPatch();
return;
case CEntityOrder::ORDER_START_CONSTRUCTION:
{
CEventStartConstruction evt( current->m_data[0].entity );
m_orderQueue.pop_front();
DispatchEvent( &evt );
}
break;
case CEntityOrder::ORDER_PRODUCE:
processProduce( current );
m_orderQueue.pop_front();
break;
case CEntityOrder::ORDER_GENERIC_NOPATHING:
if( processGenericNoPathing( current, timestep ) )
break;
updateCollisionPatch();
return;
case CEntityOrder::ORDER_GOTO_WAYPOINT:
if ( processGotoWaypoint( current, timestep, false ) )
break;
updateCollisionPatch();
return;
case CEntityOrder::ORDER_GOTO_WAYPOINT_CONTACT:
if ( processGotoWaypoint( current, timestep, true ) )
break;
updateCollisionPatch();
return;
case CEntityOrder::ORDER_GOTO:
case CEntityOrder::ORDER_RUN:
if( processGoto( current, timestep ) )
break;
updateCollisionPatch();
return;
case CEntityOrder::ORDER_PATROL:
if( processPatrol( current, timestep ) )
break;
updateCollisionPatch();
return;
case CEntityOrder::ORDER_PATH_END_MARKER:
m_orderQueue.pop_front();
break;
default:
debug_warn( "Invalid entity order" );
}
}
if( m_orderQueue.empty() )
{
// If we have no orders, stop running
m_isRunning = false;
m_shouldRun = false;
}
PROFILE_END( "state processing" );
// If we get to here, it means we're idle or dead (no orders); update the animation
if( m_actor )
{
PROFILE( "animation updates" );
if( m_extant )
{
if( ( m_lastState != -1 ) || !m_actor->GetModel()->GetAnimation() )
{
m_actor->SetEntitySelection( L"idle" );
m_actor->SetRandomAnimation( "idle" );
}
}
}
if( m_lastState != -1 )
{
PROFILE( "state transition event" );
CEntity* d0;
CVector3D d1;
CEventOrderTransition evt( m_lastState, -1, d0, d1 );
DispatchEvent( &evt );
m_lastState = -1;
}
}
void CEntity::updateCollisionPatch()
{
vector<CEntity*>* newPatch = g_EntityManager.getCollisionPatch( this );
if( newPatch != m_collisionPatch )
{
if( m_collisionPatch )
{
// remove ourselves from old patch
vector<CEntity*>& old = *m_collisionPatch;
if( old.size() == 1 )
{
// we were the only ones there, just pop us
old.pop_back();
}
else
{
// find our location and put in the last guy in the patch, then pop back
for( size_t i=0; i < old.size(); i++ )
{
if( old[i] == this )
{
old[i] = old.back();
old.pop_back();
break;
}
}
}
}
if( m_extant )
{
// add ourselves to new patch
newPatch->push_back( this );
m_collisionPatch = newPatch;
}
}
}
#if AURA_TEST
void CEntity::UpdateAuras( size_t timestep_millis )
{
std::vector<SAura>::iterator it_a;
for( it_a = m_Auras.begin(); it_a != m_Auras.end(); it_a++ )
{
SAuraData& d = it_a->m_Data;
std::set<CEntity*>
& inRange = GetEntitiesWithinRange( m_position, d.m_Radius );
std::vector<CEntity*>::iterator it1 = inRange.begin();
std::vector<SAuraInstance>::iterator it2 = it_a->m_Influenced.begin();
while( WORLD_IS_ROUND )
{
if( it1 == inRange.end() )
{
// No more in range => anything else in the influenced set must have gone
// out of range.
for( ; it2 != it_a->m_Influenced.end(); it2++ )
UpdateAuras_LeaveRange( *it_a, it2->GetEntity() );
break;
}
if( it2 == it_a->m_Influenced.end() )
{
// Everything else in the in-range set has only just come into range
for( ; it1 != inRange.end(); it1++ )
UpdateAuras_EnterRange( *it_a, *it );
break;
}
CEntity* e1 = *it1, e2 = it2->GetEntity();
if( e1 < e2 )
{
// A new entity e1 has just come into range.
// Check to see if it can be affected by the aura.
UpdateAuras_EnterRange( *it_a, e1 );
++it1;
}
else if( e1 == e2 )
{
// The entity e1/e2 was previously in range, and still is.
UpdateAuras_Normal( *it_a, e1 );
++it1;
++it2;
}
else
{
// The entity e2 was previously in range, but is no longer.
UpdateAuras_LeaveRange( *it_a, e2 );
++it2;
}
}
}
}
void UpdateAuras_EnterRange( SAura& aura, CEntity* e )
{
if( aura.m_Recharge )
return( false );
// Check to see if the entity is eligable
if( !UpdateAuras_IsEligable( aura.m_Data, e ) )
return; // Do nothing.
SAuraInstance ai;
ai.m_Influenced = e;
ai.m_EnteredRange = ai.m_LastInRange = 0;
ai.m_Applied = -1;
// If there's no timer, apply the effect now.
if( aura.m_Data.m_Time == 0 )
{
e->ApplyAuraEffect( aura.m_Data );
ai.m_Applied = 0;
aura.m_Recharge = aura.m_Data.m_Cooldown;
}
aura.m_Influenced.push_back( ai );
}
void UpdateAuras_Normal( SAura& aura, CEntity* e )
{
// Is the entity no longer eligable?
if( !UpdateAuras_IsEligable( aura.m_Data, e ) )
{
//}
}
bool UpdateAuras_IsEligable( SAuraData& aura, CEntity* e )
{
if( e == this )
{
if( !( aura.m_Allegiance & SAuraData::SELF ) )
return( false );
}
else if( e->m_player == GetGaiaPlayer() )
{
if( !( aura.m_Allegiance & SAuraData::GAIA ) )
return( false );
}
else if( e->m_player == m_player )
{
if( !( aura.m_Allegiance & SAuraData::PLAYER ) )
return( false );
}
// TODO: Allied players
else
{
if( !( aura.m_Allegiance & SAuraData::ENEMY ) )
return( false );
}
if( e->m_hp > e->m_hp_max * aura.m_Hitpoints )
return( false );
return( true );
}
#endif
bool CEntity::Initialize()
{
CEventInitialize evt;
if( !DispatchEvent( &evt ) )
{
kill();
return false;
}
return true;
}
void CEntity::Tick()
{
CEventTick evt;
DispatchEvent( &evt );
}
void CEntity::clearOrders()
{
if ( m_orderQueue.empty() )
return;
CIdleEvent evt( m_orderQueue.front(), m_currentNotification );
DispatchEvent(&evt);
m_orderQueue.clear();
}
void CEntity::popOrder()
{
if ( m_orderQueue.empty() )
return;
CIdleEvent evt( m_orderQueue.front(), m_currentNotification );
DispatchEvent(&evt);
m_orderQueue.pop_front();
}
void CEntity::pushOrder( CEntityOrder& order )
{
CEventPrepareOrder evt( order.m_data[0].entity, order.m_type, order.m_data[1].data, order.m_data[0].string );
if( DispatchEvent(&evt) )
{
m_orderQueue.push_back( order );
if(evt.m_notifyType != CEntityListener::NOTIFY_NONE)
{
CheckListeners( evt.m_notifyType, evt.m_notifySource );
}
}
}
void CEntity::DispatchNotification( CEntityOrder order, int type )
{
CEventNotification evt( order, type );
DispatchEvent( &evt );
}
struct isListenerSender
{
CEntity* sender;
isListenerSender(CEntity* sender) : sender(sender) {}
bool operator()(CEntityListener& listener)
{
return listener.m_sender == sender;
}
};
int CEntity::DestroyNotifier( CEntity* target )
{
if ( target->m_listeners.empty() )
return 0;
//Stop listening
// (Don't just loop and use 'erase', because modifying the deque while
// looping over it is a bit dangerous)
std::deque<CEntityListener>::iterator newEnd = std::remove_if(
target->m_listeners.begin(), target->m_listeners.end(),
isListenerSender(this));
target->m_listeners.erase(newEnd, target->m_listeners.end());
//Get rid of our copy
std::vector<CEntity*>::iterator newEnd2 = std::remove_if(
m_notifiers.begin(), m_notifiers.end(),
bind2nd(std::equal_to<CEntity*>(), target));
int removed = std::distance(newEnd2, m_notifiers.end());
m_notifiers.erase(newEnd2, m_notifiers.end());
return removed;
}
void CEntity::DestroyAllNotifiers()
{
debug_assert(m_destroyNotifiers);
//Make them stop listening to us
while ( ! m_notifiers.empty() )
DestroyNotifier( m_notifiers[0] );
}
CEntityFormation* CEntity::GetFormation()
{
if ( m_formation < 0 )
return NULL;
return g_FormationManager.GetFormation(m_formation);
}
void CEntity::DispatchFormationEvent( int type )
{
CFormationEvent evt( type );
DispatchEvent( &evt );
}
void CEntity::repath()
{
CVector2D destination;
if( m_orderQueue.empty() )
return;
while( !m_orderQueue.empty() &&
( ( m_orderQueue.front().m_type == CEntityOrder::ORDER_GOTO_COLLISION )
|| ( m_orderQueue.front().m_type == CEntityOrder::ORDER_GOTO_NOPATHING )
|| ( m_orderQueue.front().m_type == CEntityOrder::ORDER_GOTO_SMOOTHED ) ) )
{
destination = m_orderQueue.front().m_data[0].location;
m_orderQueue.pop_front();
}
g_Pathfinder.requestPath( me, destination );
}
void CEntity::reorient()
{
m_orientation = m_graphics_orientation;
m_ahead.x = sin( m_orientation.Y );
m_ahead.y = cos( m_orientation.Y );
if( m_bounds->m_type == CBoundingObject::BOUND_OABB )
((CBoundingBox*)m_bounds)->setOrientation( m_ahead );
updateActorTransforms();
}
void CEntity::teleport()
{
m_position = m_graphics_position;
m_bounds->setPosition( m_position.X, m_position.Z );
updateCollisionPatch();
repath();
}
void CEntity::playerChanged()
{
if( m_actor )
m_actor->SetPlayerID( m_player->GetPlayerID() );
}
void CEntity::checkSelection()
{
if( m_selected )
{
if( !g_Selection.isSelected( me ) )
g_Selection.addSelection( me );
}
else
{
if( g_Selection.isSelected( me ) )
g_Selection.removeSelection( me );
}
}
void CEntity::checkGroup()
{
g_Selection.changeGroup( me, -1 ); // Ungroup
if( ( m_grouped >= 0 ) && ( m_grouped < MAX_GROUPS ) )
g_Selection.changeGroup( me, m_grouped );
}
void CEntity::interpolate( float relativeoffset )
{
CVector3D old_graphics_position = m_graphics_position;
CVector3D old_graphics_orientation = m_graphics_orientation;
m_graphics_position = Interpolate<CVector3D>( m_position_previous, m_position, relativeoffset );
// Avoid wraparound glitches for interpolating angles.
m_orientation.X = fmodf(m_orientation.X, 2*PI); // (ensure the following loops can't take forever)
m_orientation.Y = fmodf(m_orientation.Y, 2*PI);
m_orientation.Z = fmodf(m_orientation.Z, 2*PI);
while( m_orientation.Y < m_orientation_previous.Y - PI )
m_orientation_previous.Y -= 2 * PI;
while( m_orientation.Y > m_orientation_previous.Y + PI )
m_orientation_previous.Y += 2 * PI;
while( m_orientation.X < m_orientation_previous.X - PI )
m_orientation_previous.X -= 2 * PI;
while( m_orientation.X > m_orientation_previous.X + PI )
m_orientation_previous.X += 2 * PI;
while( m_orientation.Z < m_orientation_previous.Z - PI )
m_orientation_previous.Z -= 2 * PI;
while( m_orientation.Z > m_orientation_previous.Z + PI )
m_orientation_previous.Z += 2 * PI;
m_graphics_orientation = Interpolate<CVector3D>( m_orientation_previous, m_orientation, relativeoffset );
// Mark the actor transform data as invalid if the entity has moved since
// the last call to 'interpolate'.
// position.Y is ignored because we can't determine the new value without
// calling snapToGround, which is slow. TODO: This may need to be adjusted to
// handle flying units or moving terrain.
if( m_graphics_orientation != old_graphics_orientation ||
m_graphics_position.X != old_graphics_position.X ||
m_graphics_position.Z != old_graphics_position.Z
)
{
m_actor_transform_valid = false;
}
// Update the actor transform data when necessary.
if( !m_actor_transform_valid )
{
snapToGround();
updateActorTransforms();
m_actor_transform_valid = true;
}
}
void CEntity::invalidateActor()
{
m_actor_transform_valid = false;
}
void CEntity::render()
{
if( !m_orderQueue.empty() )
{
std::deque<CEntityOrder>::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();
float groundLevel = pTerrain->getExactGroundLevel( x, z );
if( m_anchorType==L"Ground" )
{
return groundLevel;
}
else
{
return max( groundLevel, g_Renderer.GetWaterManager()->m_WaterHeight );
}
}
int CEntity::findSector( int divs, float angle, float maxAngle, bool negative )
{
float step=maxAngle/divs;
if ( negative )
{
float tracker;
int i=1, sectorRemainder;
for ( tracker=-maxAngle/2.0f; tracker+step<0.0f; tracker+=step, ++i )
{
if ( angle > tracker && angle <= tracker+step )
return i;
}
sectorRemainder = i;
i=divs;
for ( tracker=maxAngle/2.0f; tracker-step>0.0f; tracker-=step, --i )
{
if ( angle < tracker && angle >= tracker-step )
return i;
}
return sectorRemainder;
}
else
{
int i=1;
for ( float tracker=0.0f; tracker<maxAngle; tracker+=step, ++i )
{
if ( angle > tracker && angle <= tracker+step )
return i;
}
}
debug_warn("CEntity::findSector() - invalid parameters passed.");
return -1;
}
void CEntity::renderSelectionOutline( float alpha )
{
if( !m_bounds )
return;
if( getCollisionObject( this ) )
glColor4f( 1.0f, 0.5f, 0.5f, alpha );
else
{
const SPlayerColour& col = m_player->GetColour();
glColor3f( col.r, col.g, col.b );
}
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();
}
CVector2D CEntity::getScreenCoords( float height )
{
CCamera &g_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) + height;
g_Camera.GetScreenCoordinates(above, sx, sy);
return CVector2D( sx, sy );
}
void CEntity::renderBarBorders()
{
if ( m_staminaBarHeight >= 0 &&
g_Selection.m_unitUITextures.find(m_healthBorderName) != g_Selection.m_unitUITextures.end() )
{
ogl_tex_bind( g_Selection.m_unitUITextures[m_healthBorderName] );
CVector2D pos = getScreenCoords( m_healthBarHeight );
float left = pos.x - m_healthBorderWidth/2;
float right = pos.x + m_healthBorderWidth/2;
pos.y = g_yres - pos.y;
float bottom = pos.y + m_healthBorderHeight/2;
float top = pos.y - 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_staminaBarHeight >= 0 &&
g_Selection.m_unitUITextures.find(m_staminaBorderName) != g_Selection.m_unitUITextures.end() )
{
ogl_tex_bind( g_Selection.m_unitUITextures[m_staminaBorderName] );
CVector2D pos = getScreenCoords( m_staminaBarHeight );
float left = pos.x - m_staminaBorderWidth/2;
float right = pos.x + m_staminaBorderWidth/2;
pos.y = g_yres - pos.y;
float bottom = pos.y + m_staminaBorderHeight/2;
float top = pos.y - 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 )
return;
if( 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_healthBarHeight );
float x1 = pos.x - m_healthBarSize/2;
float x2 = pos.x + m_healthBarSize/2;
float y = g_yres - pos.y;
glLineWidth( m_healthBarWidth );
glBegin(GL_LINES);
// green part of bar
glColor3f( 0, 1, 0 );
glVertex3f( x1, y, 0 );
glColor3f( 0, 1, 0 );
glVertex3f( x1 + m_healthBarSize*fraction, y, 0 );
// red part of bar
glColor3f( 1, 0, 0 );
glVertex3f( x1 + m_healthBarSize*fraction, y, 0 );
glColor3f( 1, 0, 0 );
glVertex3f( x2, y, 0 );
glEnd();
glLineWidth(1.0f);
}
void CEntity::renderStaminaBar()
{
if( !m_bounds )
return;
if( 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_staminaBarHeight );
float x1 = pos.x - m_staminaBarSize/2;
float x2 = pos.x + m_staminaBarSize/2;
float y = g_yres - pos.y;
glLineWidth( 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_staminaBarSize*fraction, y, 0 );
// purple part of bar
glColor3f( 0.3f, 0, 0.3f );
glVertex3f( x1 + 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 )
return;
if( 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 *g_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_rankHeight;
g_Camera->GetScreenCoordinates(above, sx, sy);
int size = m_rankWidth/2;
float x1 = sx + m_healthBarSize/2;
float x2 = sx + 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_hasRallyPoint || g_Selection.m_unitUITextures.find(m_rallyTexture) ==
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_rallyTexture] );
sprite.SetTexture(&tex);
CVector3D rally = m_rallyPoint;
rally.Y += m_rallyHeight/2.f + .1f;
sprite.SetTranslation(rally);
sprite.Render();
}
void CEntity::CalculateRun(float timestep)
{
if( m_staminaMax > 0 )
{
if ( m_isRunning && m_runDecayRate > 0 )
{
m_staminaCurr = max( 0.0f, m_staminaCurr - timestep / 1000.0f / m_runDecayRate * m_staminaMax );
}
else if ( m_orderQueue.empty() && m_runRegenRate > 0 )
{
m_staminaCurr = min( m_staminaMax, m_staminaCurr + timestep / 1000.0f / m_runRegenRate * m_staminaMax );
}
}
}
void CEntity::CalculateHealth(float timestep)
{
if ( m_healthDecay && m_healthDecayRate > 0 )
{
m_healthCurr = max( 0.0f, m_healthCurr - timestep / 1000.0f / m_healthDecayRate * m_healthMax );
}
else if ( m_healthRegenRate > 0 && g_Game->GetTime() - m_lastCombatTime > m_healthRegenStart )
{
m_healthCurr = min( m_healthMax, m_healthCurr + timestep / 1000.0f / m_healthRegenRate * m_healthMax );
}
}
/*
Scripting interface
*/
// Scripting initialization
void CEntity::ScriptingInit()
{
AddMethod<jsval, &CEntity::ToString>( "toString", 0 );
AddMethod<bool, &CEntity::OrderSingle>( "order", 1 );
AddMethod<bool, &CEntity::OrderQueued>( "orderQueued", 1 );
AddMethod<jsval, &CEntity::TerminateOrder>( "terminateOrder", 1 );
AddMethod<bool, &CEntity::Kill>( "kill", 0 );
AddMethod<bool, &CEntity::IsIdle>( "isIdle", 0 );
AddMethod<bool, &CEntity::HasClass>( "hasClass", 1 );
AddMethod<jsval, &CEntity::GetSpawnPoint>( "getSpawnPoint", 1 );
AddMethod<jsval, &CEntity::AddAura>( "addAura", 3 );
AddMethod<jsval, &CEntity::RemoveAura>( "removeAura", 1 );
AddMethod<jsval, &CEntity::SetActionParams>( "setActionParams", 5 );
AddMethod<int, &CEntity::GetCurrentRequest>( "getCurrentRequest", 0 );
AddMethod<bool, &CEntity::ForceCheckListeners>( "forceCheckListeners", 2 );
AddMethod<bool, &CEntity::RequestNotification>( "requestNotification", 4 );
AddMethod<jsval, &CEntity::DestroyNotifier>( "destroyNotifier", 1 );
AddMethod<jsval, &CEntity::DestroyAllNotifiers>( "destroyAllNotifiers", 0 );
AddMethod<jsval, &CEntity::TriggerRun>( "triggerRun", 1 );
AddMethod<jsval, &CEntity::SetRun>( "setRun", 1 );
AddMethod<jsval, &CEntity::GetRunState>( "getRunState", 0 );
AddMethod<bool, &CEntity::IsInFormation>( "isInFormation", 0 );
AddMethod<jsval, &CEntity::GetFormationBonus>( "getFormationBonus", 0 );
AddMethod<jsval, &CEntity::GetFormationBonusType>( "getFormationBonusType", 0 );
AddMethod<jsval, &CEntity::GetFormationBonusVal>( "getFormationBonusVal", 0 );
AddMethod<jsval, &CEntity::GetFormationPenalty>( "getFormationPenalty", 0 );
AddMethod<jsval, &CEntity::GetFormationPenaltyType>( "getFormationPenaltyType", 0 );
AddMethod<jsval, &CEntity::GetFormationPenaltyVal>( "getFormationPenaltyVal", 0 );
AddMethod<jsval, &CEntity::RegisterDamage>( "registerDamage", 0 );
AddMethod<jsval, &CEntity::RegisterOrderChange>( "registerOrderChange", 0 );
AddMethod<jsval, &CEntity::GetAttackDirections>( "getAttackDirections", 0 );
AddMethod<jsval, &CEntity::FindSector>("findSector", 4);
AddMethod<jsval, &CEntity::GetHeight>("getHeight", 0 );
AddMethod<jsval, &CEntity::HasRallyPoint>("hasRallyPoint", 0 );
AddMethod<jsval, &CEntity::SetRallyPoint>("setRallyPoint", 0 );
AddMethod<jsval, &CEntity::GetRallyPoint>("getRallyPoint", 0 );
AddClassProperty( L"template", (CBaseEntity* CEntity::*)&CEntity::m_base, false, (NotifyFn)&CEntity::loadBase );
AddClassProperty( L"traits.id.classes", (GetFn)&CEntity::getClassSet, (SetFn)&CEntity::setClassSet );
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* jsBaseEntity = JSVAL_TO_OBJECT( argv[0] );
CStrW templateName;
CPlayer* player = g_Game->GetPlayer( 0 );
CBaseEntity* baseEntity = NULL;
if( JSVAL_IS_OBJECT( argv[0] ) ) // only set baseEntity if jsBaseEntity is a valid object
baseEntity = ToNative<CBaseEntity>( cx, jsBaseEntity );
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<CStrW> selections; // TODO: let scripts specify selections?
HEntity handle = g_EntityManager.create( baseEntity, position, orientation, selections );
handle->SetPlayer( player );
handle->Initialize();
*rval = ToJSVal<CEntity>( *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() ) ) );
}
bool CEntity::Order( JSContext* cx, uintN argc, jsval* argv, bool Queued )
{
// This needs to be sorted (uses Scheduler rather than network messaging)
debug_assert( argc >= 1 );
int orderCode;
try
{
orderCode = ToPrimitive<int>( 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:
if( argc < 3 )
{
JS_ReportError( cx, "Too few parameters" );
return( false );
}
try
{
newOrder.m_data[0].location.x = ToPrimitive<float>( argv[1] );
newOrder.m_data[0].location.y = ToPrimitive<float>( argv[2] );
}
catch( PSERROR_Scripting_ConversionFailed )
{
JS_ReportError( cx, "Invalid location" );
return( false );
}
if ( orderCode == CEntityOrder::ORDER_RUN )
m_triggerRun = true;
//It's not a notification order
if ( argc == 3 )
{
if ( m_destroyNotifiers )
{
m_currentRequest=0;
DestroyAllNotifiers();
}
}
break;
case CEntityOrder::ORDER_GENERIC:
if( argc < 3 )
{
JS_ReportError( cx, "Too few parameters" );
return( false );
}
target = ToNative<CEntity>( 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<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 ( m_destroyNotifiers )
{
m_currentRequest=0;
DestroyAllNotifiers();
}
}
break;
case CEntityOrder::ORDER_PRODUCE:
if( argc < 3 )
{
JS_ReportError( cx, "Too few parameters" );
return( false );
}
try {
newOrder.m_data[0].string = ToPrimitive<CStrW>(argv[2]);
newOrder.m_data[1].data = 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 );
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( L"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 )
{
CBaseEntity* be = ToNative<CBaseEntity>( 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 CEntity::AddAura( JSContext* cx, uintN argc, jsval* argv )
{
debug_assert( argc >= 4 );
debug_assert( JSVAL_IS_OBJECT(argv[3]) );
CStrW name = ToPrimitive<CStrW>( argv[0] );
float radius = ToPrimitive<float>( argv[1] );
size_t tickRate = max( 0, ToPrimitive<int>( argv[2] ) ); // since it's a size_t we don't want it to be negative
JSObject* handler = JSVAL_TO_OBJECT( argv[3] );
if( m_auras[name] )
{
delete m_auras[name];
}
m_auras[name] = new CAura( cx, this, name, radius, tickRate, handler );
return JSVAL_VOID;
}
jsval 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);
}
return JSVAL_VOID;
}
jsval 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( minRange, maxRange, speed, animation );
return JSVAL_VOID;
}
bool CEntity::RequestNotification( JSContext* cx, uintN argc, jsval* argv )
{
if( argc < 4 )
{
JS_ReportError( cx, "Too few parameters" );
return( false );
}
CEntityListener notify;
CEntity *target = ToNative<CEntity>( argv[0] );
(int&)notify.m_type = ToPrimitive<int>( argv[1] );
bool tmpDestroyNotifiers = ToPrimitive<bool>( argv[2] );
m_destroyNotifiers = !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) && m_destroyNotifiers )
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 )
{
if( argc < 2 )
{
JS_ReportError( cx, "Too few parameters" );
return false;
}
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 CEntity::DestroyAllNotifiers( JSContext* UNUSED(cx), uintN UNUSED(argc), jsval* UNUSED(argv) )
{
DestroyAllNotifiers();
return JS_TRUE;
}
jsval CEntity::DestroyNotifier( JSContext* cx, uintN argc, jsval* argv )
{
if ( argc < 1 )
{
JS_ReportError(cx, "too few parameters: CEntity::DestroyNotifier");
return JS_FALSE;
}
DestroyNotifier( ToNative<CEntity>( argv[0] ) );
return JS_TRUE;
}
jsval CEntity::TriggerRun( JSContext* UNUSED(cx), uintN UNUSED(argc), jsval* UNUSED(argv) )
{
m_triggerRun = true;
return JSVAL_VOID;
}
jsval CEntity::SetRun( JSContext* cx, uintN argc, jsval* argv )
{
if( argc < 1 )
{
JS_ReportError( cx, "Too few parameters" );
return( false );
}
m_shouldRun = ToPrimitive<bool> ( argv[0] );
m_isRunning = m_shouldRun;
return JSVAL_VOID;
}
jsval CEntity::GetRunState( JSContext* UNUSED(cx), uintN UNUSED(argc), jsval* UNUSED(argv) )
{
return BOOLEAN_TO_JSVAL( m_shouldRun );
}
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 )
{
if ( argc < 1 )
{
JS_ReportError( cx, "Too few parameters" );
return( false );
}
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_sectorDivs, angle, DEGTORAD(360.0f))-1;
m_sectorValues[sector]=true;
return JS_TRUE;
}
jsval CEntity::RegisterOrderChange( JSContext* cx, uintN argc, jsval* argv )
{
if ( argc < 1 )
{
JS_ReportError( cx, "Too few parameters" );
return( false );
}
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 = MAX( 0.0, findSector(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<bool>::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 )
{
if ( argc < 4 )
{
JS_ReportError( cx, "Too few parameters" );
return( false );
}
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) );
}
jsval CEntity::HasRallyPoint( JSContext* UNUSED(cx), uintN UNUSED(argc), jsval* UNUSED(argv) )
{
return ToJSVal( m_hasRallyPoint );
}
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) )
{
m_hasRallyPoint = true;
m_rallyPoint = g_Game->GetView()->GetCamera()->GetWorldCoordinates();
return JS_TRUE;
}