1
0
forked from 0ad/0ad
0ad/source/simulation/Entity.cpp

1126 lines
28 KiB
C++
Raw Normal View History

/* 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"
# Added tool for viewing models and animations outside the game. Atlas: Added ActorViewer. Moved GL canvas into separate class for shared use. Disabled message-handling callback while blocked on the game, and stopped creating dialog boxes inside the game thread in order to avoid deadlocks (hopefully). Support multiple Views (for independent sets of camera/update/render code). Recalculate territory boundaries when necessary. Changed default list of animations to match those currently used by actors. # Tidied up more code. Moved some more #includes out of .h files, to minimise unnecessary compilation. MathUtil: Deleted unused/unuseful macros (M_PI (use PI instead), M_PI_2 (use PI/2), MAX3, ABS (use abs)). ObjectManager: Removed some ScEd-specific things. Unit: Moved creation out of UnitManager, so units can be created without adding to the manager. Changed CStr8 to the more conventional CStr. app_hooks: Removed warning for setting multiple times. win: Restored SEH catcher. GameSetup, GameView: Removed RenderNoCull, because it doesn't seem to do what it says it does ("force renderer to load everything") since we're loading-on-demand most stuff and it doesn't seem especially useful since we'd prefer to minimise loading times (but feel free to correct me if I'm wrong). (And because it crashes when things need to be initialised in a different order, so it's easier to remove than to understand and fix it.) PatchRData, Renderer: Work sensibly when there's no game (hence no LOS manager, water, etc). LOSManager: Use entity position instead of actor position when possible. TerritoryManager: Allow delayed recalculations (so Atlas can issue lots of move+recalculate commands per frame). Cinematic: Non-pointer wxTimer, so it doesn't leak and doesn't have to be deleted manually. This was SVN commit r4261.
2006-08-28 19:36:42 +02:00
#include "graphics/GameView.h"
#include "graphics/Model.h"
# Added tool for viewing models and animations outside the game. Atlas: Added ActorViewer. Moved GL canvas into separate class for shared use. Disabled message-handling callback while blocked on the game, and stopped creating dialog boxes inside the game thread in order to avoid deadlocks (hopefully). Support multiple Views (for independent sets of camera/update/render code). Recalculate territory boundaries when necessary. Changed default list of animations to match those currently used by actors. # Tidied up more code. Moved some more #includes out of .h files, to minimise unnecessary compilation. MathUtil: Deleted unused/unuseful macros (M_PI (use PI instead), M_PI_2 (use PI/2), MAX3, ABS (use abs)). ObjectManager: Removed some ScEd-specific things. Unit: Moved creation out of UnitManager, so units can be created without adding to the manager. Changed CStr8 to the more conventional CStr. app_hooks: Removed warning for setting multiple times. win: Restored SEH catcher. GameSetup, GameView: Removed RenderNoCull, because it doesn't seem to do what it says it does ("force renderer to load everything") since we're loading-on-demand most stuff and it doesn't seem especially useful since we'd prefer to minimise loading times (but feel free to correct me if I'm wrong). (And because it crashes when things need to be initialised in a different order, so it's easier to remove than to understand and fix it.) PatchRData, Renderer: Work sensibly when there's no game (hence no LOS manager, water, etc). LOSManager: Use entity position instead of actor position when possible. TerritoryManager: Allow delayed recalculations (so Atlas can issue lots of move+recalculate commands per frame). Cinematic: Non-pointer wxTimer, so it doesn't leak and doesn't have to be deleted manually. This was SVN commit r4261.
2006-08-28 19:36:42 +02:00
#include "graphics/Sprite.h"
#include "graphics/Terrain.h"
# Added tool for viewing models and animations outside the game. Atlas: Added ActorViewer. Moved GL canvas into separate class for shared use. Disabled message-handling callback while blocked on the game, and stopped creating dialog boxes inside the game thread in order to avoid deadlocks (hopefully). Support multiple Views (for independent sets of camera/update/render code). Recalculate territory boundaries when necessary. Changed default list of animations to match those currently used by actors. # Tidied up more code. Moved some more #includes out of .h files, to minimise unnecessary compilation. MathUtil: Deleted unused/unuseful macros (M_PI (use PI instead), M_PI_2 (use PI/2), MAX3, ABS (use abs)). ObjectManager: Removed some ScEd-specific things. Unit: Moved creation out of UnitManager, so units can be created without adding to the manager. Changed CStr8 to the more conventional CStr. app_hooks: Removed warning for setting multiple times. win: Restored SEH catcher. GameSetup, GameView: Removed RenderNoCull, because it doesn't seem to do what it says it does ("force renderer to load everything") since we're loading-on-demand most stuff and it doesn't seem especially useful since we'd prefer to minimise loading times (but feel free to correct me if I'm wrong). (And because it crashes when things need to be initialised in a different order, so it's easier to remove than to understand and fix it.) PatchRData, Renderer: Work sensibly when there's no game (hence no LOS manager, water, etc). LOSManager: Use entity position instead of actor position when possible. TerritoryManager: Allow delayed recalculations (so Atlas can issue lots of move+recalculate commands per frame). Cinematic: Non-pointer wxTimer, so it doesn't leak and doesn't have to be deleted manually. This was SVN commit r4261.
2006-08-28 19:36:42 +02:00
#include "graphics/Unit.h"
#include "graphics/UnitManager.h"
#include "maths/MathUtil.h"
# Added tool for viewing models and animations outside the game. Atlas: Added ActorViewer. Moved GL canvas into separate class for shared use. Disabled message-handling callback while blocked on the game, and stopped creating dialog boxes inside the game thread in order to avoid deadlocks (hopefully). Support multiple Views (for independent sets of camera/update/render code). Recalculate territory boundaries when necessary. Changed default list of animations to match those currently used by actors. # Tidied up more code. Moved some more #includes out of .h files, to minimise unnecessary compilation. MathUtil: Deleted unused/unuseful macros (M_PI (use PI instead), M_PI_2 (use PI/2), MAX3, ABS (use abs)). ObjectManager: Removed some ScEd-specific things. Unit: Moved creation out of UnitManager, so units can be created without adding to the manager. Changed CStr8 to the more conventional CStr. app_hooks: Removed warning for setting multiple times. win: Restored SEH catcher. GameSetup, GameView: Removed RenderNoCull, because it doesn't seem to do what it says it does ("force renderer to load everything") since we're loading-on-demand most stuff and it doesn't seem especially useful since we'd prefer to minimise loading times (but feel free to correct me if I'm wrong). (And because it crashes when things need to be initialised in a different order, so it's easier to remove than to understand and fix it.) PatchRData, Renderer: Work sensibly when there's no game (hence no LOS manager, water, etc). LOSManager: Use entity position instead of actor position when possible. TerritoryManager: Allow delayed recalculations (so Atlas can issue lots of move+recalculate commands per frame). Cinematic: Non-pointer wxTimer, so it doesn't leak and doesn't have to be deleted manually. This was SVN commit r4261.
2006-08-28 19:36:42 +02:00
#include "maths/scripting/JSInterface_Vector3D.h"
#include "ps/Game.h"
#include "ps/Profile.h"
#include "renderer/Renderer.h"
#include "renderer/WaterManager.h"
# Added tool for viewing models and animations outside the game. Atlas: Added ActorViewer. Moved GL canvas into separate class for shared use. Disabled message-handling callback while blocked on the game, and stopped creating dialog boxes inside the game thread in order to avoid deadlocks (hopefully). Support multiple Views (for independent sets of camera/update/render code). Recalculate territory boundaries when necessary. Changed default list of animations to match those currently used by actors. # Tidied up more code. Moved some more #includes out of .h files, to minimise unnecessary compilation. MathUtil: Deleted unused/unuseful macros (M_PI (use PI instead), M_PI_2 (use PI/2), MAX3, ABS (use abs)). ObjectManager: Removed some ScEd-specific things. Unit: Moved creation out of UnitManager, so units can be created without adding to the manager. Changed CStr8 to the more conventional CStr. app_hooks: Removed warning for setting multiple times. win: Restored SEH catcher. GameSetup, GameView: Removed RenderNoCull, because it doesn't seem to do what it says it does ("force renderer to load everything") since we're loading-on-demand most stuff and it doesn't seem especially useful since we'd prefer to minimise loading times (but feel free to correct me if I'm wrong). (And because it crashes when things need to be initialised in a different order, so it's easier to remove than to understand and fix it.) PatchRData, Renderer: Work sensibly when there's no game (hence no LOS manager, water, etc). LOSManager: Use entity position instead of actor position when possible. TerritoryManager: Allow delayed recalculations (so Atlas can issue lots of move+recalculate commands per frame). Cinematic: Non-pointer wxTimer, so it doesn't leak and doesn't have to be deleted manually. This was SVN commit r4261.
2006-08-28 19:36:42 +02:00
#include "scripting/ScriptableComplex.inl"
#include "Aura.h"
#include "Collision.h"
#include "Entity.h"
#include "EntityFormation.h"
# Added tool for viewing models and animations outside the game. Atlas: Added ActorViewer. Moved GL canvas into separate class for shared use. Disabled message-handling callback while blocked on the game, and stopped creating dialog boxes inside the game thread in order to avoid deadlocks (hopefully). Support multiple Views (for independent sets of camera/update/render code). Recalculate territory boundaries when necessary. Changed default list of animations to match those currently used by actors. # Tidied up more code. Moved some more #includes out of .h files, to minimise unnecessary compilation. MathUtil: Deleted unused/unuseful macros (M_PI (use PI instead), M_PI_2 (use PI/2), MAX3, ABS (use abs)). ObjectManager: Removed some ScEd-specific things. Unit: Moved creation out of UnitManager, so units can be created without adding to the manager. Changed CStr8 to the more conventional CStr. app_hooks: Removed warning for setting multiple times. win: Restored SEH catcher. GameSetup, GameView: Removed RenderNoCull, because it doesn't seem to do what it says it does ("force renderer to load everything") since we're loading-on-demand most stuff and it doesn't seem especially useful since we'd prefer to minimise loading times (but feel free to correct me if I'm wrong). (And because it crashes when things need to be initialised in a different order, so it's easier to remove than to understand and fix it.) PatchRData, Renderer: Work sensibly when there's no game (hence no LOS manager, water, etc). LOSManager: Use entity position instead of actor position when possible. TerritoryManager: Allow delayed recalculations (so Atlas can issue lots of move+recalculate commands per frame). Cinematic: Non-pointer wxTimer, so it doesn't leak and doesn't have to be deleted manually. This was SVN commit r4261.
2006-08-28 19:36:42 +02:00
#include "EntityManager.h"
#include "EntityTemplate.h"
#include "EntityTemplateCollection.h"
#include "EventHandlers.h"
#include "Formation.h"
# Added tool for viewing models and animations outside the game. Atlas: Added ActorViewer. Moved GL canvas into separate class for shared use. Disabled message-handling callback while blocked on the game, and stopped creating dialog boxes inside the game thread in order to avoid deadlocks (hopefully). Support multiple Views (for independent sets of camera/update/render code). Recalculate territory boundaries when necessary. Changed default list of animations to match those currently used by actors. # Tidied up more code. Moved some more #includes out of .h files, to minimise unnecessary compilation. MathUtil: Deleted unused/unuseful macros (M_PI (use PI instead), M_PI_2 (use PI/2), MAX3, ABS (use abs)). ObjectManager: Removed some ScEd-specific things. Unit: Moved creation out of UnitManager, so units can be created without adding to the manager. Changed CStr8 to the more conventional CStr. app_hooks: Removed warning for setting multiple times. win: Restored SEH catcher. GameSetup, GameView: Removed RenderNoCull, because it doesn't seem to do what it says it does ("force renderer to load everything") since we're loading-on-demand most stuff and it doesn't seem especially useful since we'd prefer to minimise loading times (but feel free to correct me if I'm wrong). (And because it crashes when things need to be initialised in a different order, so it's easier to remove than to understand and fix it.) PatchRData, Renderer: Work sensibly when there's no game (hence no LOS manager, water, etc). LOSManager: Use entity position instead of actor position when possible. TerritoryManager: Allow delayed recalculations (so Atlas can issue lots of move+recalculate commands per frame). Cinematic: Non-pointer wxTimer, so it doesn't leak and doesn't have to be deleted manually. This was SVN commit r4261.
2006-08-28 19:36:42 +02:00
#include "FormationManager.h"
#include "PathfindEngine.h"
#include "ProductionQueue.h"
#include "Simulation.h"
#include "Stance.h"
#include "TechnologyCollection.h"
# Added tool for viewing models and animations outside the game. Atlas: Added ActorViewer. Moved GL canvas into separate class for shared use. Disabled message-handling callback while blocked on the game, and stopped creating dialog boxes inside the game thread in order to avoid deadlocks (hopefully). Support multiple Views (for independent sets of camera/update/render code). Recalculate territory boundaries when necessary. Changed default list of animations to match those currently used by actors. # Tidied up more code. Moved some more #includes out of .h files, to minimise unnecessary compilation. MathUtil: Deleted unused/unuseful macros (M_PI (use PI instead), M_PI_2 (use PI/2), MAX3, ABS (use abs)). ObjectManager: Removed some ScEd-specific things. Unit: Moved creation out of UnitManager, so units can be created without adding to the manager. Changed CStr8 to the more conventional CStr. app_hooks: Removed warning for setting multiple times. win: Restored SEH catcher. GameSetup, GameView: Removed RenderNoCull, because it doesn't seem to do what it says it does ("force renderer to load everything") since we're loading-on-demand most stuff and it doesn't seem especially useful since we'd prefer to minimise loading times (but feel free to correct me if I'm wrong). (And because it crashes when things need to be initialised in a different order, so it's easier to remove than to understand and fix it.) PatchRData, Renderer: Work sensibly when there's no game (hence no LOS manager, water, etc). LOSManager: Use entity position instead of actor position when possible. TerritoryManager: Allow delayed recalculations (so Atlas can issue lots of move+recalculate commands per frame). Cinematic: Non-pointer wxTimer, so it doesn't leak and doesn't have to be deleted manually. This was SVN commit r4261.
2006-08-28 19:36:42 +02:00
#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;
2006-02-13 04:32:15 +01:00
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
}
}
2005-01-23 23:38:13 +01:00
void CEntity::SetPlayer(CPlayer *pPlayer)
{
m_player = pPlayer;
2005-01-23 23:38:13 +01:00
// 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;
2005-01-23 23:38:13 +01:00
}
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
2005-12-29 10:12:54 +01:00
{
entf_clear(ENTF_TRANSITION);
2005-12-29 10:12:54 +01:00
}
switch( current->m_type )
{
case CEntityOrder::ORDER_GOTO_NOPATHING:
case CEntityOrder::ORDER_GOTO_COLLISION:
case CEntityOrder::ORDER_GOTO_SMOOTHED:
if( ProcessGotoNoPathing( current, timestep ) )
2005-12-29 10:12:54 +01:00
break;
UpdateCollisionPatch();
2005-12-29 10:12:54 +01:00
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 ) )
2005-12-29 10:12:54 +01:00
break;
UpdateCollisionPatch();
2005-12-29 10:12:54 +01:00
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 ) )
2005-12-29 10:12:54 +01:00
break;
UpdateCollisionPatch();
2005-12-29 10:12:54 +01:00
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 ) )
2005-12-29 10:12:54 +01:00
break;
UpdateCollisionPatch();
2005-12-29 10:12:54 +01:00
return;
case CEntityOrder::ORDER_PATROL:
if( ProcessPatrol( current, timestep ) )
2005-12-29 10:12:54 +01:00
break;
UpdateCollisionPatch();
2005-12-29 10:12:54 +01:00
return;
case CEntityOrder::ORDER_PATH_END_MARKER:
2005-12-29 10:12:54 +01:00
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()
2005-12-29 10:12:54 +01:00
{
return true;
2005-12-29 10:12:54 +01:00
}
/*
2005-12-29 10:12:54 +01:00
void CEntity::Tick()
{
CEventTick evt;
DispatchEvent( &evt );
2005-12-29 10:12:54 +01:00
}
*/
void CEntity::ClearOrders()
2005-12-29 10:12:54 +01:00
{
if ( m_orderQueue.empty() )
return;
CIdleEvent evt( m_orderQueue.front(), m_currentNotification );
DispatchEvent(&evt);
m_orderQueue.clear();
2005-12-29 10:12:54 +01:00
}
void CEntity::PopOrder()
{
if ( m_orderQueue.empty() )
return;
CIdleEvent evt( m_orderQueue.front(), m_currentNotification );
DispatchEvent(&evt);
2005-12-29 10:12:54 +01:00
m_orderQueue.pop_front();
}
void CEntity::PushOrder( const CEntityOrder& order )
2005-12-29 10:12:54 +01:00
{
CEventPrepareOrder evt( order.m_target_entity, order.m_type, order.m_action, order.m_name );
2006-02-13 04:32:15 +01:00
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 );
}
}
}
2005-12-29 10:12:54 +01:00
}
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 )
2006-02-13 04:32:15 +01:00
{
//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] );
2006-02-13 04:32:15 +01:00
}
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()
2005-12-29 10:12:54 +01:00
{
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 );
2005-12-29 10:12:54 +01:00
}
void CEntity::Reorient()
2005-12-29 10:12:54 +01:00
{
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();
2005-12-29 10:12:54 +01:00
}
void CEntity::Teleport()
2005-12-29 10:12:54 +01:00
{
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();
2005-12-29 10:12:54 +01:00
}
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()
2005-12-29 10:12:54 +01:00
{
/*
if( m_selected )
{
if( !g_Selection.IsSelected( me ) )
g_Selection.AddSelection( me );
}
else
{
if( g_Selection.IsSelected( me ) )
g_Selection.RemoveSelection( me );
}
*/
2005-12-29 10:12:54 +01:00
}
void CEntity::CheckGroup()
2005-12-29 10:12:54 +01:00
{
/*
g_Selection.ChangeGroup( me, -1 ); // Ungroup
if( ( m_grouped >= 0 ) && ( m_grouped < MAX_GROUPS ) )
g_Selection.ChangeGroup( me, m_grouped );
*/
2005-12-29 10:12:54 +01:00
}
void CEntity::Interpolate( float relativeoffset )
2005-12-29 10:12:54 +01:00
{
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 );
2005-12-29 10:12:54 +01:00
// 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;
}
2005-12-29 10:12:54 +01:00
}
void CEntity::InvalidateActor()
2005-12-29 10:12:54 +01:00
{
m_actor_transform_valid = false;
2005-12-29 10:12:54 +01:00
}
float CEntity::GetAnchorLevel( float x, float z )
2005-12-29 10:12:54 +01:00
{
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 );
}
2005-12-29 10:12:54 +01:00
}
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();
}