1
0
forked from 0ad/0ad
0ad/source/simulation/Entity.cpp
2010-05-20 18:09:23 +00:00

1126 lines
28 KiB
C++

/* Copyright (C) 2009 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* 0 A.D. is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with 0 A.D. If not, see <http://www.gnu.org/licenses/>.
*/
#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/Profile.h"
#include "renderer/Renderer.h"
#include "renderer/WaterManager.h"
#include "scripting/ScriptableComplex.inl"
#include "Aura.h"
#include "Collision.h"
#include "Entity.h"
#include "EntityFormation.h"
#include "EntityManager.h"
#include "EntityTemplate.h"
#include "EntityTemplateCollection.h"
#include "EventHandlers.h"
#include "Formation.h"
#include "FormationManager.h"
#include "PathfindEngine.h"
#include "ProductionQueue.h"
#include "Simulation.h"
#include "Stance.h"
#include "TechnologyCollection.h"
#include "TerritoryManager.h"
#include <algorithm>
#include "ps/GameSetup/Config.h"
const float MAX_ROTATION_RATE = 2*(float)M_PI; // radians per second
CEntity::CEntity( CEntityTemplate* base, CVector3D position, float orientation, const std::set<CStr8>& actorSelections, const CStrW* building )
{
ent_flags = 0;
m_position = position;
m_orientation.X = 0;
m_orientation.Y = orientation;
m_orientation.Z = 0;
m_ahead.x = sin( m_orientation.Y );
m_ahead.y = cos( m_orientation.Y );
m_position_previous = m_position;
m_orientation_smoothed = m_orientation_previous = m_orientation;
m_player = NULL;
m_productionQueue = new CProductionQueue( this );
m_stance = new CHoldStance( this );
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;
entf_set(ENTF_TRANSITION);
m_fsm_cyclepos = NOT_IN_CYCLE;
m_base = base;
m_actorSelections = actorSelections;
LoadBase();
if( m_bounds )
m_bounds->SetPosition( m_position.X, m_position.Z );
m_graphics_position = m_position;
m_graphics_orientation = m_orientation;
m_actor_transform_valid = false;
entf_clear(ENTF_HAS_RALLY_POINT);
entf_clear(ENTF_DESTROYED);
m_selected = false;
entf_clear(ENTF_IS_RUNNING);
entf_clear(ENTF_SHOULD_RUN);
entf_clear(ENTF_TRIGGER_RUN);
entf_clear(ENTF_HEALTH_DECAY);
m_frameCheck = 0;
m_lastCombatTime = -100;
m_lastRunTime = -100;
m_currentNotification = 0;
m_currentRequest = 0;
entf_set(ENTF_DESTROY_NOTIFIERS);
m_formationSlot = (size_t)-1;
m_formation = -1;
m_grouped = -1;
if( building )
m_building = *building;
m_extant = true;
m_visible = true;
m_rallyPoint = m_position;
m_associatedTerritory = NULL;
m_player = g_Game->GetPlayer( 0 );
}
CEntity::~CEntity()
{
if( m_actor )
{
g_Game->GetWorld()->GetUnitManager().RemoveUnit( m_actor );
delete( m_actor );
}
if( m_bounds )
{
delete( m_bounds );
}
delete m_productionQueue;
delete m_stance;
for( AuraTable::iterator it = m_auras.begin(); it != m_auras.end(); it++ )
{
delete it->second;
}
m_auras.clear();
entf_set(ENTF_DESTROY_NOTIFIERS);
for ( size_t i=0; i<m_listeners.size(); i++ )
m_listeners[i].m_sender->DestroyNotifier( this );
m_listeners.clear();
DestroyAllNotifiers();
CEntity* remove = this;
g_FormationManager.RemoveUnit(remove);
}
void CEntity::LoadBase()
{
size_t previous_unit_id = invalidUnitId;
if( m_actor )
{
previous_unit_id = m_actor->GetID();
g_Game->GetWorld()->GetUnitManager().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_Game->GetWorld()->GetUnitManager().CreateUnit( actorName, this, m_actorSelections );
if( m_actor )
m_actor->SetID( previous_unit_id );
// 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() );
}
// Re-enter all our auras so they can take into account our new traits
ExitAuras();
// Resize sectors array
m_sectorValues.resize(m_base->m_sectorDivs);
for ( int i=0; i<m_base->m_sectorDivs; ++i )
m_sectorValues[i] = false;
}
void CEntity::initAuraData()
{
if ( m_auras.empty() )
return;
m_unsnappedPoints.resize(m_auras.size());
size_t i=0;
for ( AuraTable::iterator it=m_auras.begin(); it!=m_auras.end(); ++it, ++i )
{
m_unsnappedPoints[i].resize(AURA_CIRCLE_POINTS);
float radius = it->second->m_radius;
for ( int j=0; j<AURA_CIRCLE_POINTS; ++j )
{
float val = j * 2*(float)M_PI / AURA_CIRCLE_POINTS;
m_unsnappedPoints[i][j] = CVector2D( cosf(val)*radius,
sinf(val)*radius );
}
}
}
void CEntity::removeObstacle()
{
#ifdef USE_DCDT
if(g_Pathfinder.dcdtInitialized)
{
g_Pathfinder.dcdtPathfinder.remove_polygon(m_dcdtId);
g_Pathfinder.dcdtPathfinder.DeleteAbstraction();
g_Pathfinder.dcdtPathfinder.Abstract();
if(g_ShowPathfindingOverlay)
{
g_Pathfinder.drawTriangulation();
}
}
#endif // USE_DCDT
}
void CEntity::Kill(bool keepActor)
{
if( entf_get( ENTF_DESTROYED ) )
{
return; // We were already killed this frame
}
entf_set(ENTF_DESTROYED);
CEventDeath evt;
DispatchEvent( &evt );
g_FormationManager.RemoveUnit(this);
entf_set(ENTF_DESTROY_NOTIFIERS);
for ( size_t i=0; i<m_listeners.size(); i++ )
m_listeners[i].m_sender->DestroyNotifier( this );
m_listeners.clear();
DestroyAllNotifiers();
for( AuraTable::iterator it = m_auras.begin(); it != m_auras.end(); it++ )
{
it->second->RemoveAll();
delete it->second;
}
m_auras.clear();
ExitAuras();
ClearOrders();
SAFE_DELETE(m_bounds);
m_extant = false;
UpdateCollisionPatch();
//Kai: added to remove the entity in the polygon soup (for triangulation)
removeObstacle();
// g_Selection.RemoveAll( me );
// g_EntityManager.m_refd[me.m_handle] = false; // refd must be made false when DESTROYED is set
// g_EntityManager.SetDeath(true); // remember that a unit died this frame
//
// g_EntityManager.RemoveUnitCount(this); //Decrease population
// If we have a death animation and want to keep the actor, play that animation
if( keepActor && m_actor &&
m_actor->HasAnimation( "death" ) )
{
// Prevent "wiggling" as we try to interpolate between here and our death position (if we were moving)
m_graphics_position = m_position;
m_position_previous = m_position;
m_graphics_orientation = m_orientation;
m_orientation_smoothed = m_orientation;
m_orientation_previous = m_orientation;
SnapToGround();
// Conform to the ground
CVector2D targetXZ = g_Game->GetWorld()->GetTerrain()->GetSlopeAngleFace(this);
m_orientation.X = clamp( targetXZ.x, -1.0f, 1.0f );
m_orientation.Z = clamp( targetXZ.y, -1.0f, 1.0f );
m_orientation_unclamped.x = targetXZ.x;
m_orientation_unclamped.y = targetXZ.y;
UpdateActorTransforms();
m_actor_transform_valid = true;
// Play death animation and keep the actor in the game in a dead state
// (TODO: remove the actor after some time through some kind of fading mechanism)
m_actor->SetAnimationState( "death", true, 1.f, 0.f, false, L"" );
}
else
{
g_Game->GetWorld()->GetUnitManager().DeleteUnit( m_actor );
m_actor = NULL;
me = HEntity(); // Will deallocate the entity, assuming nobody else has a reference to it
}
}
void CEntity::SetPlayer(CPlayer *pPlayer)
{
m_player = pPlayer;
// This should usually be called CUnit::SetPlayerID, so we don't need to
// update the actor here.
// If we're a territory centre, change the territory's owner
if( m_associatedTerritory )
m_associatedTerritory->owner = pPlayer;
}
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 );
}
void CEntity::UpdateXZOrientation()
{
// Make sure m_ahead is correct
m_ahead.x = sin( m_orientation.Y );
m_ahead.y = cos( m_orientation.Y );
CVector2D targetXZ = g_Game->GetWorld()->GetTerrain()->GetSlopeAngleFace(this);
if ( !m_base )
{
return;
}
m_orientation.X = clamp( targetXZ.x, -m_base->m_anchorConformX, m_base->m_anchorConformX );
m_orientation.Z = clamp( targetXZ.y, -m_base->m_anchorConformZ, m_base->m_anchorConformZ );
m_orientation_unclamped.x = targetXZ.x;
m_orientation_unclamped.y = targetXZ.y;
}
jsval CEntity::GetClassSet()
{
CStrW result = m_classes.GetMemberList();
return( ToJSVal( result ) );
}
void CEntity::SetClassSet( jsval value )
{
CStr memberCmdList = ToPrimitive<CStrW>( value );
m_classes.SetFromMemberList(memberCmdList);
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( int timestep )
{
if( !m_extant ) return;
m_position_previous = m_position;
m_orientation_previous = m_orientation_smoothed;
CalculateRegen( timestep );
if ( entf_get(ENTF_TRIGGER_RUN) )
m_frameCheck++;
if ( m_frameCheck != 0 )
{
entf_set(ENTF_SHOULD_RUN);
entf_clear(ENTF_TRIGGER_RUN);
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" );
UpdateOrders( timestep );
// Calculate smoothed rotation: rotate around Y by at most MAX_ROTATION_RATE per second
float delta = m_orientation.Y - m_orientation_smoothed.Y;
// Wrap delta to -M_PI..M_PI
delta = fmod(delta + (float)M_PI, 2*(float)M_PI); // range -2PI..2PI
if (delta < 0) delta += 2*(float)M_PI; // range 0..2PI
delta -= (float)M_PI; // range -M_PI..M_PI
// Clamp to max rate
float deltaClamped = clamp(delta, -MAX_ROTATION_RATE*timestep/1000.f, +MAX_ROTATION_RATE*timestep/1000.f);
// Calculate new orientation, in a peculiar way in order to make sure the
// result gets close to m_orientation (rather than being n*2*M_PI out)
float newY = m_orientation.Y + deltaClamped - delta;
// Apply the smoothed rotation
m_orientation_smoothed = CVector3D(
m_orientation.X,
newY,
m_orientation.Z
);
}
void CEntity::UpdateOrders( int timestep )
{
// 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( entf_get(ENTF_IS_RUNNING) )
{
m_lastRunTime = g_Game->GetSimulation()->GetTime();
}
if( m_orderQueue.empty() )
{
// We are idle. Tell our stance in case it wants us to do something.
PROFILE( "unit ai" );
m_stance->OnIdle();
}
while( !m_orderQueue.empty() )
{
CEntityOrder* current = &m_orderQueue.front();
CStr name = me;
#ifdef DEBUG_SYNCHRONIZATION
debug_printf(L"Order for %ls: %d (src %d)\n",
m_base->m_Tag.c_str(), current->m_type, current->m_source);
#endif
if( current->m_type != m_lastState )
{
entf_set(ENTF_TRANSITION);
m_fsm_cyclepos = NOT_IN_CYCLE;
PROFILE( "state transition / order" );
CEntity* target = NULL;
if( current->m_target_entity )
target = &( *( current->m_target_entity ) );
CVector3D worldPosition = (CVector3D)current->m_target_location;
CEventOrderTransition evt( m_lastState, current->m_type, target, worldPosition );
if( !DispatchEvent( &evt ) )
{
m_orderQueue.pop_front();
continue;
}
else if( target )
{
current->m_target_location = worldPosition;
current->m_target_entity = target->me;
}
m_lastState = current->m_type;
}
else
{
entf_clear(ENTF_TRANSITION);
}
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_GOTO_NOPATHING_CONTACT:
if( ProcessGotoNoPathingContact( current, timestep ) )
break;
UpdateCollisionPatch();
return;
case CEntityOrder::ORDER_CONTACT_ACTION:
// Choose to run if and only if order.m_run is set
entf_set_to(ENTF_TRIGGER_RUN, current->m_run);
if( !entf_get(ENTF_TRIGGER_RUN) )
entf_set_to(ENTF_SHOULD_RUN, false);
if( ProcessContactAction( current, timestep, true ) )
break;
UpdateCollisionPatch();
return;
case CEntityOrder::ORDER_START_CONSTRUCTION:
{
CEventStartConstruction evt( current->m_new_obj );
m_orderQueue.pop_front();
DispatchEvent( &evt );
}
break;
case CEntityOrder::ORDER_PRODUCE:
ProcessProduce( current );
m_orderQueue.pop_front();
break;
case CEntityOrder::ORDER_CONTACT_ACTION_NOPATHING:
if( ProcessContactActionNoPathing( 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:
// Choose to run if and only if type == ORDER_RUN
entf_set_to(ENTF_TRIGGER_RUN, current->m_type == CEntityOrder::ORDER_RUN);
if( !entf_get(ENTF_TRIGGER_RUN) )
entf_set_to(ENTF_SHOULD_RUN, false);
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( L"Invalid entity order" );
}
}
if( m_orderQueue.empty() )
{
// If we have no orders, stop running
entf_clear(ENTF_IS_RUNNING);
entf_clear(ENTF_SHOULD_RUN);
}
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->SetAnimationState( "idle", false, 1.f, 0.f, false, L"" );
}
}
}
if( m_lastState != -1 )
{
PROFILE( "state transition event" );
CVector3D vec(0, 0, 0);
CEventOrderTransition evt( m_lastState, -1, 0, vec );
DispatchEvent( &evt );
m_lastState = -1;
}
}
void CEntity::UpdateCollisionPatch()
{
}
#if AURA_TEST
void CEntity::UpdateAuras( int 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()
{
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( const CEntityOrder& order )
{
CEventPrepareOrder evt( order.m_target_entity, order.m_type, order.m_action, order.m_name );
if( DispatchEvent(&evt) )
{
if (order.m_type == CEntityOrder::ORDER_SET_RALLY_POINT)
{
// It doesn't make sense to queue this type of order; just set the rally point
entf_set(ENTF_HAS_RALLY_POINT);
m_rallyPoint = order.m_target_location;
}
else if (order.m_type == CEntityOrder::ORDER_SET_STANCE)
{
// It doesn't make sense to queue this type of order; just set the stance
m_stanceName = order.m_name;
StanceChanged();
}
else
{
m_orderQueue.push_back( order );
if(evt.m_notifyType != CEntityListener::NOTIFY_NONE)
{
CheckListeners( evt.m_notifyType, evt.m_notifySource );
}
}
}
}
void CEntity::DispatchNotification( const 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 )
{
//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 = m_notifiers.end() - newEnd2;
//m_notifiers.erase(newEnd2, m_notifiers.end());
m_notifiers.resize(m_notifiers.size() - removed);
return removed;
}
void CEntity::DestroyAllNotifiers()
{
debug_assert(entf_get(ENTF_DESTROY_NOTIFIERS));
//Make them stop listening to us
while ( ! m_notifiers.empty() )
DestroyNotifier( m_notifiers[m_notifiers.size()-1] );
}
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()
{
debug_printf(L"Repath\n");
CVector2D destination;
CEntityOrder::EOrderSource orderSource = CEntityOrder::SOURCE_PLAYER;
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_target_location;
orderSource = m_orderQueue.front().m_source;
m_orderQueue.pop_front();
}
g_Pathfinder.RequestPath( me, destination, orderSource );
}
void CEntity::Reorient()
{
m_graphics_orientation = m_orientation;
m_orientation_previous = m_orientation;
m_orientation_smoothed = m_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_previous = m_position;
m_graphics_position = m_position;
if (m_bounds)
m_bounds->SetPosition( m_position.X, m_position.Z );
UpdateActorTransforms();
UpdateCollisionPatch();
// TODO: Repath breaks things - entities get sent to (0,0) if they're moved in
// Atlas. I can't see Teleport being used anywhere else important, so
// hopefully it won't hurt to just remove it for now...
// Repath();
}
void CEntity::StanceChanged()
{
delete m_stance;
m_stance = 0;
if( m_stanceName == "aggress" )
{
m_stance = new CAggressStance( this );
}
else if( m_stanceName == "defend" )
{
m_stance = new CDefendStance( this );
}
else if( m_stanceName == "stand" )
{
m_stance = new CStandStance( this );
}
else // m_stanceName == "hold" or undefined stance
{
m_stance = new CHoldStance( this );
}
}
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;
relativeoffset = clamp( relativeoffset, 0.f, 1.f );
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*(float)M_PI); // (ensure the following loops can't take forever)
m_orientation.Y = fmodf(m_orientation.Y, 2*(float)M_PI);
m_orientation.Z = fmodf(m_orientation.Z, 2*(float)M_PI);
while( m_orientation.Y < m_orientation_previous.Y - (float)M_PI )
m_orientation_previous.Y -= 2 * (float)M_PI;
while( m_orientation.Y > m_orientation_previous.Y + (float)M_PI )
m_orientation_previous.Y += 2 * (float)M_PI;
while( m_orientation.X < m_orientation_previous.X - (float)M_PI )
m_orientation_previous.X -= 2 * (float)M_PI;
while( m_orientation.X > m_orientation_previous.X + (float)M_PI )
m_orientation_previous.X += 2 * (float)M_PI;
while( m_orientation.Z < m_orientation_previous.Z - (float)M_PI )
m_orientation_previous.Z -= 2 * (float)M_PI;
while( m_orientation.Z > m_orientation_previous.Z + (float)M_PI )
m_orientation_previous.Z += 2 * (float)M_PI;
UpdateXZOrientation();
m_graphics_orientation = ::Interpolate<CVector3D>( m_orientation_previous, m_orientation_smoothed, 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;
}
float CEntity::GetAnchorLevel( float x, float z )
{
CTerrain *pTerrain = g_Game->GetWorld()->GetTerrain();
float groundLevel = pTerrain->GetExactGroundLevel( x, z );
if( m_base && m_base->m_anchorType==L"Ground" )
{
return groundLevel;
}
else
{
return std::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(L"CEntity::FindSector() - invalid parameters passed.");
return -1;
}
static inline float regen(float cur, float limit, float timestep, float regen_rate)
{
if(regen_rate <= 0)
return cur;
return std::min(limit, cur + timestep / 1000.0f * regen_rate * limit );
}
static inline float decay(float cur, float limit, float timestep, float decay_rate)
{
if(decay_rate <= 0)
return cur;
return std::max(0.0f, cur - timestep / 1000.0f * decay_rate * limit);
}
void CEntity::CalculateRegen(float timestep)
{
// Health regen
if(entf_get(ENTF_HEALTH_DECAY))
m_healthCurr = decay(m_healthCurr, m_healthMax, timestep, m_healthDecayRate);
else if(g_Game->GetSimulation()->GetTime() - m_lastCombatTime > m_healthRegenStart)
m_healthCurr = regen(m_healthCurr, m_healthMax, timestep, m_healthRegenRate);
// Stamina regen
if( m_staminaMax > 0 )
{
if(entf_get(ENTF_IS_RUNNING))
m_staminaCurr = decay(m_staminaCurr, m_staminaMax, timestep, m_runDecayRate);
else if(m_orderQueue.empty())
m_staminaCurr = regen(m_staminaCurr, m_staminaMax, timestep, m_runRegenRate);
}
}
void CEntity::ExitAuras()
{
for( AuraSet::iterator it = m_aurasInfluencingMe.begin(); it != m_aurasInfluencingMe.end(); it++ )
{
(*it)->Remove( this );
}
m_aurasInfluencingMe.clear();
}