/* Copyright (c) 2002-2012 Croteam Ltd. 
This program is free software; you can redistribute it and/or modify
it under the terms of version 2 of the GNU General Public License as published by
the Free Software Foundation


This program 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 this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */

#include <Engine/StdH.h>

#include <Engine/Entities/Entity.h>
#include <Engine/Entities/EntityClass.h>
#include <Engine/Entities/EntityProperties.h>
#include <Engine/Entities/LastPositions.h>
#include <Engine/Entities/EntityCollision.h>
#include <Engine/Entities/Precaching.h>
#include <Engine/Entities/ShadingInfo.h>
#include <Engine/Light/LightSource.h>

#include <Engine/Math/Geometry.inl>
#include <Engine/Math/Clipping.inl>
#include <Engine/Math/Float.h>
#include <Engine/Math/OBBox.h>
#include <Engine/Math/Functions.h>

#include <Engine/Base/CRC.h>
#include <Engine/Base/Console.h>
#include <Engine/Base/Statistics_Internal.h>
#include <Engine/Network/Network.h>
#include <Engine/Network/PlayerTarget.h>
#include <Engine/Network/SessionState.h>
#include <Engine/Brushes/Brush.h>
#include <Engine/Brushes/BrushTransformed.h>
#include <Engine/Brushes/BrushArchive.h>
#include <Engine/Terrain/TerrainArchive.h>
#include <Engine/World/World.h>
#include <Engine/World/WorldRayCasting.h>
#include <Engine/World/PhysicsProfile.h>
#include <Engine/Base/ReplaceFile.h>
#include <Engine/Entities/InternalClasses.h>
#include <Engine/Models/ModelObject.h>
#include <Engine/Sound/SoundData.h>
#include <Engine/Sound/SoundObject.h>
#include <Engine/Graphics/Texture.h>
#include <Engine/Ska/Render.h>
#include <Engine/Terrain/Terrain.h>
#include <Engine/Terrain/TerrainRayCasting.h>
#include <Engine/Terrain/TerrainMisc.h>

#include <Engine/Base/ListIterator.inl>

#include <Engine/Templates/BSP.h>
#include <Engine/Templates/DynamicArray.cpp>
#include <Engine/Templates/DynamicContainer.cpp>
#include <Engine/Templates/StaticArray.cpp>
#include <Engine/Templates/StaticStackArray.cpp>

#include <Engine/Templates/Stock_CAnimData.h>
#include <Engine/Templates/Stock_CTextureData.h>
#include <Engine/Templates/Stock_CModelData.h>
#include <Engine/Templates/Stock_CSoundData.h>

// a reference to a void event for use as default parameter
const EVoid _evVoid;
const CEntityEvent &_eeVoid = _evVoid;

// allocation step for state stack of a CRationalEntity
#define STATESTACK_ALLOCATIONSTEP 5
extern INDEX _ctEntities;
extern INDEX _ctPredictorEntities;

// check if entity is of given class
BOOL IsOfClass(CEntity *pen, const char *pstrClassName)
{
  if (pen==NULL || pstrClassName==NULL) {
    return FALSE;
  }
  if (strcmp(pen->GetClass()->ec_pdecDLLClass->dec_strName, pstrClassName)==0) {
    return TRUE;
  } else {
    return FALSE;
  }
}
BOOL IsOfSameClass(CEntity *pen1, CEntity *pen2)
{
  if (pen1==NULL || pen2==NULL) {
    return FALSE;
  }
  if (pen1->GetClass()->ec_pdecDLLClass == pen2->GetClass()->ec_pdecDLLClass) {
    return TRUE;
  } else {
    return FALSE;
  }
}

// check if entity is of given class or derived from
BOOL IsDerivedFromClass(CEntity *pen, const char *pstrClassName)
{
  if (pen==NULL || pstrClassName==NULL) {
    return FALSE;
  }
  // for all classes in hierarchy of the entity
  for(CDLLEntityClass *pdecDLLClass = pen->GetClass()->ec_pdecDLLClass;
      pdecDLLClass!=NULL;
      pdecDLLClass = pdecDLLClass->dec_pdecBase) {
    // if it is the wanted class
    if (strcmp(pdecDLLClass->dec_strName, pstrClassName)==0) {
      // it is derived
      return TRUE;
    }
  }
  // otherwise, it is not derived
  return FALSE;
}

/////////////////////////////////////////////////////////////////////
// CEntity

/*
 * Default constructor.
 */
CEntity::CEntity(void)
{
  en_pbrBrush = NULL;
  en_psiShadingInfo = NULL;
  en_pciCollisionInfo = NULL;
  en_pecClass = NULL;
  en_ulFlags = 0;
  en_ulSpawnFlags = 0xFFFFFFFFL;    // active always
  en_ulPhysicsFlags = 0;
  en_ulCollisionFlags = 0;
  en_ctReferences = 0;
  en_ulID = 0;
  en_RenderType = RT_NONE;
  en_fSpatialClassificationRadius = -1.0f;
  en_penParent = NULL;
  en_plpLastPositions = NULL;
  _ctEntities++;
}

/*
 * Destructor.
 */
CEntity::~CEntity(void)
{
  ASSERT(en_ctReferences==0);
  ASSERT(en_ulID!=0);
  ASSERT(en_RenderType==RT_NONE);
  // remove it from container in its world
  ASSERT(!en_pwoWorld->wo_cenEntities.IsMember(this));
  en_pwoWorld->wo_cenAllEntities.Remove(this);

  // unset spatial clasification
  en_rdSectors.Clear();

  /*
  Models are always destructed on End(), but brushes and terrains are not, so
  if the pointer is not NULL, then it must be a brush or terrain.
  Both of them are derived from CBrushBase so GetBrushType() will return its real type 
  */

  // if it is brush of terrain
  if(en_pbrBrush != NULL) {
    INDEX btType = en_pbrBrush->GetBrushType();
    // if this is brush3d
    if(btType==CBrushBase::BT_BRUSH3D) {
      // free the brush
      en_pwoWorld->wo_baBrushes.ba_abrBrushes.Delete(en_pbrBrush);
      en_pbrBrush = NULL;
    // if this is terrain
    } else if(btType==CBrushBase::BT_TERRAIN) {
      // free the brush
      en_pwoWorld->wo_taTerrains.ta_atrTerrains.Delete(en_ptrTerrain);
      en_pbrBrush = NULL;
    // unknown type
    } else {
      ASSERTALWAYS("Unsupported brush type");
    }
  }
  // clear entity type
  en_RenderType = RT_NONE;
  if(en_pecClass != NULL) en_pecClass->RemReference();
  en_pecClass = NULL;

  en_fSpatialClassificationRadius = -1.0f;
  _ctEntities--;

  if (IsPredictable()) {
    if (en_pwoWorld->wo_cenPredictable.IsMember(this)) {
      en_pwoWorld->wo_cenPredictable.Remove(this);
    }
  }
  if (en_ulFlags&ENF_WILLBEPREDICTED) {
    if (en_pwoWorld->wo_cenWillBePredicted.IsMember(this)) {
      en_pwoWorld->wo_cenWillBePredicted.Remove(this);
    }
  }
  if (IsPredictor()) {
    if (en_pwoWorld->wo_cenPredictor.IsMember(this)) {
      en_pwoWorld->wo_cenPredictor.Remove(this);
      _ctPredictorEntities--;
    }
  }
}

/////////////////////////////////////////////////////////////////////
// Access functions

/* Test if the entity is an empty brush. */
BOOL CEntity::IsEmptyBrush(void) const
{
  // if it is not brush
  if (en_RenderType != CEntity::RT_BRUSH && en_RenderType != RT_FIELDBRUSH) {
    // it is not empty brush
    return FALSE;
  // if it is brush
  } else {

    // get its brush
    CBrush3D &brBrush = *en_pbrBrush;
    // get the first mip of the brush
    CBrushMip *pbmMip = brBrush.GetFirstMip();

    // it is empty if it has zero sectors
    return pbmMip->bm_abscSectors.Count()==0;
  }
}

/* Return max Game Players */
INDEX CEntity::GetMaxPlayers(void) {
  return NET_MAXGAMEPLAYERS;
};

/* Return Player Entity */
CEntity *CEntity::GetPlayerEntity(INDEX iPlayer)
{
  ASSERT(iPlayer>=0 && iPlayer<GetMaxPlayers());

  CSessionState &ses = _pNetwork->ga_sesSessionState;
  if (ses.ses_apltPlayers[iPlayer].plt_bActive) {
    return ses.ses_apltPlayers[iPlayer].plt_penPlayerEntity;
  } else {
    return NULL;
  }
}

/* Get bounding box of this entity - for AI purposes only. */
void CEntity::GetBoundingBox(FLOATaabbox3D &box)
{
  if (en_pciCollisionInfo!=NULL) {
    box = en_pciCollisionInfo->ci_boxCurrent;
  } else {
    GetSize(box);
    box += GetPlacement().pl_PositionVector;
  }
}

/* Get size of this entity - for UI purposes only. */
void CEntity::GetSize(FLOATaabbox3D &box)
{
  if (en_RenderType==CEntity::RT_MODEL || en_RenderType==CEntity::RT_EDITORMODEL) {
    en_pmoModelObject->GetCurrentFrameBBox( box);
    box.StretchByVector(en_pmoModelObject->mo_Stretch);
  } else if(en_RenderType==CEntity::RT_SKAMODEL || en_RenderType==CEntity::RT_SKAEDITORMODEL) {
    GetModelInstance()->GetCurrentColisionBox( box);
    box.StretchByVector(GetModelInstance()->mi_vStretch);
  } else if (en_RenderType==CEntity::RT_TERRAIN) {
    GetTerrain()->GetAllTerrainBBox(box);
  } else if (en_RenderType==CEntity::RT_BRUSH || en_RenderType==CEntity::RT_FIELDBRUSH) {
    CBrushMip *pbm = en_pbrBrush->GetFirstMip();
    if (pbm == NULL) {
      box = FLOATaabbox3D(FLOAT3D(0,0,0), FLOAT3D(0,0,0));
    } else {
      box = pbm->bm_boxBoundingBox;
      box += -GetPlacement().pl_PositionVector;
    }
  }
  else {
    box = FLOATaabbox3D(FLOAT3D(0,0,0), FLOAT3D(0,0,0));
  }
}

/* Get name of this entity. */
const CTString &CEntity::GetName(void) const
{
  static const CTString strDummyName("");
  return strDummyName;
}
const CTString &CEntity::GetDescription(void) const // name + some more verbose data
{
  static const CTString strDummyDescription("");
  return strDummyDescription;
}

/* Get first target of this entity. */
CEntity *CEntity::GetTarget(void) const
{
  return NULL;
}

/* Check if entity can be used as a target. */
BOOL CEntity::IsTargetable(void) const
{
  // cannot be targeted unless this function is overridden
  return FALSE;
}
/* Check if entity is marker */
BOOL CEntity::IsMarker(void) const{
  // cannot be marker unless this function is overridden
  return FALSE;
}
/* Check if entity is important */
BOOL CEntity::IsImportant(void) const{
  // cannot be important unless this function is overridden
  return FALSE;
}
/* Check if entity is moved on a route set up by its targets. */
BOOL CEntity::MovesByTargetedRoute(CTString &strTargetProperty) const
{
  return FALSE;
}
/* Check if entity can drop marker for making linked route. */
BOOL CEntity::DropsMarker(CTFileName &fnmMarkerClass, CTString &strTargetProperty) const
{
  return FALSE;
}

/* Get light source information - return NULL if not a light source. */
CLightSource *CEntity::GetLightSource(void)
{
  return NULL;
}

BOOL CEntity::IsTargetValid(SLONG slPropertyOffset, CEntity *penTarget)
{
  return TRUE;
}

/* Get anim data for given animation property - return NULL for none. */
CAnimData *CEntity::GetAnimData(SLONG slPropertyOffset)
{
  return NULL;
}

/* Get force type name, return empty string if not used. */
const CTString &CEntity::GetForceName(INDEX iForce)
{
  static const CTString strDummyName("");
  return strDummyName;
}

/* Get forces in given point. */
void CEntity::GetForce(INDEX iForce, const FLOAT3D &vPoint,
  CForceStrength &fsGravity, CForceStrength &fsField)
{
  // default gravity
  fsGravity.fs_vDirection = FLOAT3D(0,-1,0);
  fsGravity.fs_fAcceleration = 9.81f;
  fsGravity.fs_fVelocity = 0;

  // no force field
  fsField.fs_fAcceleration = 0;
}
/* Get entity that controls the force, used for change notification checking. */
CEntity *CEntity::GetForceController(INDEX iForce)
{
  return NULL;
}

/* Adjust model shading parameters if needed - return TRUE if needs model shadows. */
BOOL CEntity::AdjustShadingParameters(FLOAT3D &vLightDirection,
  COLOR &colLight, COLOR &colAmbient)
{
  return TRUE;
}
/* Adjust model mip factor if needed. */
void CEntity::AdjustMipFactor(FLOAT &fMipFactor)
{
  (void)fMipFactor;
  NOTHING;
}

// get a different model object for rendering - so entity can change its appearance dynamically
// NOTE: base model is always used for other things (physics, etc).
CModelObject *CEntity::GetModelForRendering(void)
{
  return en_pmoModelObject;
}

// get a different model instance for rendering - so entity can change its appearance dynamically
// NOTE: base model is always used for other things (physics, etc).
CModelInstance *CEntity::GetModelInstanceForRendering(void)
{
  return en_pmiModelInstance;
}

/* Get fog type name, return empty string if not used. */
const CTString &CEntity::GetFogName(INDEX iFog)
{
  static const CTString strDummyName("");
  return strDummyName;
}

/* Get fog, return FALSE for none. */
BOOL CEntity::GetFog(INDEX iFog, class CFogParameters &fpFog)
{
  return FALSE;
}

/* Get haze type name, return empty string if not used. */
const CTString &CEntity::GetHazeName(INDEX iHaze)
{
  static const CTString strDummyName("");
  return strDummyName;
}

/* Get haze, return FALSE for none. */
BOOL CEntity::GetHaze(INDEX iHaze, class CHazeParameters &hpHaze, FLOAT3D &vViewDir)
{
  return FALSE;
}

/* Get mirror type name, return empty string if not used. */
const CTString &CEntity::GetMirrorName(INDEX iMirror)
{
  static const CTString strDummyName("");
  return strDummyName;
}

/* Get mirror, return FALSE for none. */
BOOL CEntity::GetMirror(INDEX iMirror, class CMirrorParameters &mpMirror)
{
  return FALSE;
}

/* Get gradient type name, return empty string if not used. */
const CTString &CEntity::GetGradientName(INDEX iGradient)
{
  static const CTString strDummyName("");
  return strDummyName;
}

/* Get gradient, return FALSE for none. */
BOOL CEntity::GetGradient(INDEX iGradient, class CGradientParameters &gpGradient)
{
  return FALSE;
}

FLOAT3D CEntity::GetClassificationBoxStretch(void)
{
  return FLOAT3D( 1.0f, 1.0f, 1.0f);
}

/* Get field information - return NULL if not a field. */
CFieldSettings *CEntity::GetFieldSettings(void)
{
  return NULL;
}
/* Render particles made by this entity. */
void CEntity::RenderParticles(void)
{
  NOTHING;
}
/* Get current collision box index for this entity. */
INDEX CEntity::GetCollisionBoxIndex(void)
{
  // by default, use only box 0
  return 0;
}
/* Get current collision box - override for custom collision boxes. */
void CEntity::GetCollisionBoxParameters(INDEX iBox, FLOATaabbox3D &box, INDEX &iEquality)
{
  // if this is ska model
  if(en_RenderType==RT_SKAMODEL || en_RenderType==RT_SKAEDITORMODEL) {
    box.minvect = GetModelInstance()->GetCollisionBoxMin(iBox);
    box.maxvect = GetModelInstance()->GetCollisionBoxMax(iBox);
    //FLOATaabbox3D boxNS = box;
    box.StretchByVector(GetModelInstance()->mi_vStretch);
    iEquality = GetModelInstance()->GetCollisionBoxDimensionEquality(iBox);
  } else {
    box.minvect = en_pmoModelObject->GetCollisionBoxMin(iBox);
    box.maxvect = en_pmoModelObject->GetCollisionBoxMax(iBox);
    box.StretchByVector(en_pmoModelObject->mo_Stretch);
    iEquality = en_pmoModelObject->GetCollisionBoxDimensionEquality(iBox);
  }
}

/* Render game view */
void CEntity::RenderGameView(CDrawPort *pdp, void *pvUserData)
{
  NOTHING;
}
// apply mirror and stretch to the entity if supported
void CEntity::MirrorAndStretch(FLOAT fStretch, BOOL bMirrorX)
{
  NOTHING;
}
// get offset for depth-sorting of alpha models (in meters, positive is nearer)
FLOAT CEntity::GetDepthSortOffset(void)
{
  return 0.0f;
}
// get visibility tweaking bits
ULONG CEntity::GetVisTweaks(void)
{
  return 0;
}

// Get max tessellation level
FLOAT CEntity::GetMaxTessellationLevel(void)
{
  return 0.0f;
}

// get pointer to your predictor/predicted
CEntity *CEntity::GetPredictionPair(void)
{
  // this should never be called, it must be overriden if prediction is used!
  ASSERT(FALSE);
  return this;  // this is safest to return if in release version
}
void CEntity::SetPredictionPair(CEntity *penPair)
{
  // this should never be called, it must be overriden if prediction is used!
  ASSERT(FALSE);
}

// add to prediction any entities that this entity depends on
void CEntity::AddDependentsToPrediction(void)
{
}

// called by other entities to set time prediction parameter
void CEntity::SetPredictionTime(TIME tmAdvance)   // give time interval in advance to set
{
  NOTHING; // by default, don't use time prediction
}

// called by engine to get the upper time limit 
TIME CEntity::GetPredictionTime(void)   // return moment in time up to which to predict this entity
{
  return -1.0f; // by default, don't use time prediction
}

// get maximum allowed range for predicting this entity
FLOAT CEntity::GetPredictionRange(void)
{
  return UpperLimit(0.0f);    // by default, cli_fPredictEntitiesRange is the limit
}

// copy for prediction
void CEntity::CopyForPrediction(CEntity &enOrg)
{
  // this should never be called, it must be overriden if prediction is used!
  ASSERT(FALSE);
}

CEntity *CEntity::GetPredictor(void)
{
  ASSERT(IsPredicted());
  CEntity *pen = GetPredictionPair();
  ASSERT(pen->IsPredictor());
  return pen;
}
CEntity *CEntity::GetPredicted(void)
{
  ASSERT(IsPredictor());
  CEntity *pen = GetPredictionPair();
  ASSERT(pen!=NULL);
  ASSERT(pen->IsPredicted());
  return pen;
}
// become predictable/unpredictable
void CEntity::SetPredictable(BOOL bON)
{
  // if predictor
  if (IsPredictor()) {
    // do nothing
    return;
  }

  // if already set
  if (IsPredictable()) {
    // check that valid
    ASSERT(en_pwoWorld->wo_cenPredictable.IsMember(this));
    // if turning on
    if (bON) {
      // do nothing
      return;
    }
    // mark as not predictable
    en_ulFlags&=~ENF_PREDICTABLE;
    // remove from container
    en_pwoWorld->wo_cenPredictable.Remove(this);

  // if not set
  } else {
    // check that valid
    ASSERT(!en_pwoWorld->wo_cenPredictable.IsMember(this));
    ASSERT(!IsPredictor());
    ASSERT(!IsPredicted());
    // if turning off
    if (!bON) {
      // do nothing
      return;
    }
    // mark as predictable
    en_ulFlags|=ENF_PREDICTABLE;
    // add to container
    en_pwoWorld->wo_cenPredictable.Add(this);
  }
}

// check if this instance is head of prediction chain
BOOL CEntity::IsPredictionHead(void)
{
  // if predicted, or will be predicted, or is a temporary predictor
  if (en_ulFlags&(ENF_PREDICTED|ENF_WILLBEPREDICTED|ENF_TEMPPREDICTOR)) {
    // it cannot be head of the chain
    return FALSE;
  }
  
  // if predictor, but not currently in the last step of prediction
  if ((en_ulFlags&ENF_PREDICTOR) && 
    _pTimer->CurrentTick()<=_pNetwork->ga_sesSessionState.ses_tmPredictionHeadTick) {
    // it cannot be head of the chain
    return FALSE;
  }
  // otherwise it is head of the chain
  return TRUE;
}

// get the prediction original (predicted), or self if not predicting
CEntity *CEntity::GetPredictionTail(void)
{
  // if this is a predictor
  if (IsPredictor()) {
    // it must be head of the prediction chain
    //ASSERT(IsPredictionHead());
    // get its predicted
    return GetPredicted();
  }
  // in other cases, return self
  return this;
}
// check if active for prediction now
BOOL CEntity::IsAllowedForPrediction(void) const
{
  return !_pNetwork->IsPredicting() || IsPredictor();
}
// check an event for prediction, returns true if already predicted
BOOL CEntity::CheckEventPrediction(ULONG ulTypeID, ULONG ulEventID)
{
  return _pNetwork->ga_sesSessionState.CheckEventPrediction(this, ulTypeID, ulEventID);
}

/* Called after creating and setting its properties. */
void CEntity::OnInitialize(const CEntityEvent &eeInput)
{
  ASSERT(GetFPUPrecision()==FPT_24BIT);

  // try to find a handler in start state
  pEventHandler pehHandler = HandlerForStateAndEvent(1, eeInput.ee_slEvent);
  // if there is a handler
  if (pehHandler!=NULL) {
    // call the function
    (this->*pehHandler)(eeInput);
  // if there is no handler
  } else {
    ASSERTALWAYS("All entities must have Main procedure!");
  }
}
/* Called before releasing entity. */
void CEntity::OnEnd(void)
{
}

// print stack to debug output
const char *CEntity::PrintStackDebug(void) 
{
  return "not CRationalEntity";
};

/*
 * Prepare entity (call after setting properties).
 */
void CEntity::Initialize(const CEntityEvent &eeInput)
{
  CSetFPUPrecision FPUPrecision(FPT_24BIT);

  // make sure we are not deleted during intialization
  CEntityPointer penThis = this;

  Initialize_internal(eeInput);
  // set spatial clasification
  FindSectorsAroundEntity();
  // precache all other things
  Precache();
}
void CEntity::Initialize_internal(const CEntityEvent &eeInput)
{
  #ifndef NDEBUG
    // clear settings for debugging
    en_RenderType = RT_ILLEGAL;
  #endif

  // remember brush zoning flag
  BOOL bWasZoning = en_ulFlags&ENF_ZONING;

  // let derived class initialize according to the properties
  OnInitialize(eeInput);
  // derived class must set all properties
//  ASSERT(en_RenderType != RT_ILLEGAL);

  // if this is a brush
  if (en_RenderType==RT_BRUSH || en_RenderType==RT_FIELDBRUSH) {
    // test if zoning
    BOOL bZoning = en_ulFlags&ENF_ZONING;
    // if switching from zoning to non-zoning
    if (bWasZoning && !bZoning) {
      // switch from zoning to non-zoning
      en_pbrBrush->SwitchToNonZoning();
      en_rdSectors.Clear();
    // if switching from non-zoning to zoning
    } else if (!bWasZoning && bZoning) {
      // switch from non-zoning to zoning
      en_pbrBrush->SwitchToZoning();
      en_rdSectors.Clear();
    }
  }

  // if it is a field brush
  CFieldSettings *pfs = GetFieldSettings();
  if (pfs!=NULL) {
    // remember its field settings
    ASSERT(en_RenderType == RT_FIELDBRUSH);
    en_pbrBrush->br_pfsFieldSettings = pfs;
  }
}

/*
 * Clean-up entity.
 */
void CEntity::End(void)
{
  CSetFPUPrecision FPUPrecision(FPT_24BIT);
  /* NOTE: Must not remove from thinker/mover list here, or CServer::ProcessGameTick()
   * might crash!
   */
  End_internal();
}
void CEntity::End_internal(void)
{
  ASSERT(GetFPUPrecision()==FPT_24BIT);

  // let derived class clean-up after itself
  OnEnd();

  // clear last positions
  if (en_plpLastPositions!=NULL) {
    delete en_plpLastPositions;
    en_plpLastPositions = NULL;
  }

  // clear spatial classification
  en_fSpatialClassificationRadius = -1.0f;
  en_boxSpatialClassification = FLOATaabbox3D();

  // depending on entity type
  switch(en_RenderType) {
  // if it is brush
  case RT_BRUSH:
    DiscardCollisionInfo();
    break;
  // if it is field brush
  case RT_FIELDBRUSH:
    DiscardCollisionInfo();
    break;

  // if it is model
  case RT_MODEL:
  case RT_EDITORMODEL:
    // free its model object
    delete en_pmoModelObject;
    delete en_psiShadingInfo;
    DiscardCollisionInfo();
    en_pmoModelObject = NULL;
    en_psiShadingInfo = NULL;
    break;
  // if it is ska model
  case RT_SKAMODEL:
  case RT_SKAEDITORMODEL:
    en_pmiModelInstance->Clear();
    delete en_pmiModelInstance;
    delete en_psiShadingInfo;
    DiscardCollisionInfo();
    en_pmiModelInstance = NULL;
    en_psiShadingInfo = NULL;
    break;
  case RT_TERRAIN:
    DiscardCollisionInfo();
    break;

  // if it is nothing
  case RT_NONE:
  case RT_VOID:
    // do nothing
    NOTHING;
    break;
  // if it is any other type
  default:
    ASSERTALWAYS("Unsupported entity type");
  }
  // clear entity type
  en_RenderType = RT_NONE;
}

/*
 * Reinitialize the entity.
 */
void CEntity::Reinitialize(void)
{
  ASSERT(GetFPUPrecision()==FPT_24BIT);
  End_internal();
  Initialize_internal(_eeVoid);
}

// teleport this entity to a new location -- takes care of telefrag damage
void CEntity::Teleport(const CPlacement3D &plNew, BOOL bTelefrag /*=TRUE*/)
{
  ASSERT(GetFPUPrecision()==FPT_24BIT);
  ASSERT(en_fSpatialClassificationRadius>0);

  // if telefragging is on and the entity has collision box
  if (bTelefrag && en_pciCollisionInfo!=NULL) {

    // create the box of the entity at its new placement
    FLOATmatrix3D mRot;
    MakeRotationMatrixFast(mRot, plNew.pl_OrientationAngle);
    FLOAT3D vPos = plNew.pl_PositionVector;
    CMovingSphere &ms0 = en_pciCollisionInfo->ci_absSpheres[0];
    CMovingSphere &ms1 = en_pciCollisionInfo->ci_absSpheres[en_pciCollisionInfo->ci_absSpheres.Count()-1];
    FLOATaabbox3D box;
    box  = FLOATaabbox3D(vPos+ms0.ms_vCenter*mRot, ms0.ms_fR);
    box |= FLOATaabbox3D(vPos+ms1.ms_vCenter*mRot, ms1.ms_fR);

    // first inflict huge damage there in the entities box
    InflictBoxDamage(this, DMT_TELEPORT, 100000.0f, box);
  }

  // remember original orientation matrix
  FLOATmatrix3D mOld = en_mRotation;

  // now put the entity there
  SetPlacement(plNew);
  // movable entity
  if (en_ulPhysicsFlags & EPF_MOVABLE) {
    ((CMovableEntity*)this)->ClearTemporaryData();
    ((CMovableEntity*)this)->en_plLastPlacement = en_plPlacement;
    // transform speeds
    FLOATmatrix3D mRel = en_mRotation*!mOld;
    ((CMovableEntity*)this)->en_vCurrentTranslationAbsolute *= mRel;
    
    if (_pNetwork->ga_ulDemoMinorVersion>=7) {
      // clear reference
      ((CMovableEntity*)this)->en_penReference = NULL;
      ((CMovableEntity*)this)->en_pbpoStandOn = NULL;
    }

    // notify that it was teleported
    SendEvent(ETeleport());
    ((CMovableEntity*)this)->AddToMovers();

    extern INDEX ent_bReportSpawnInWall;
    if (ent_bReportSpawnInWall) {
      // if movable model
      if (en_RenderType==RT_MODEL || en_RenderType==RT_EDITORMODEL || 
        en_RenderType==RT_SKAMODEL || en_RenderType==RT_SKAEDITORMODEL) {
        // check if it was teleported inside a wall
        CMovableModelEntity *pmme = (CMovableModelEntity*)this;
        CEntity *ppenObstacleDummy;
        if (pmme->CheckForCollisionNow(pmme->en_iCollisionBox, &ppenObstacleDummy)) {
          CPrintF("Entity '%s' was teleported inside a wall at (%g,%g,%g)!\n", 
            (const char *) GetName(),
            en_plPlacement.pl_PositionVector(1),
            en_plPlacement.pl_PositionVector(2),
            en_plPlacement.pl_PositionVector(3));
        }
      }
    }
  }
}

/*
 * Set placement of this entity. (use only in WEd!)
 */
void CEntity::SetPlacement(const CPlacement3D &plNew)
{
  CSetFPUPrecision FPUPrecision(FPT_24BIT);
  // check if orientation is changed
  BOOL bSameOrientation = (plNew.pl_OrientationAngle==en_plPlacement.pl_OrientationAngle);

  // if the orientation has not changed
  if (bSameOrientation) {
    // set the placement and do all needed recalculation
    SetPlacement_internal(plNew, en_mRotation, FALSE /* doesn't have to be near. */);

  // if the orientation has changed
  } else {
    // calculate new rotation matrix
    FLOATmatrix3D mRotation;
    MakeRotationMatrixFast(mRotation, plNew.pl_OrientationAngle);
    // set the placement and do all needed recalculation
    SetPlacement_internal(plNew, mRotation, FALSE /* doesn't have to be near. */);
  }

  // if this entity has parent
  if (en_penParent!=NULL) {
    // adjust relative placement
    en_plRelativeToParent = en_plPlacement;
    en_plRelativeToParent.AbsoluteToRelativeSmooth(en_penParent->en_plPlacement);
  }
}


/*
 * Fall down to floor. (use only in WEd!)
 */
void CEntity::FallDownToFloor( void)
{
  CEntity::RenderType rt = GetRenderType();
  // is this old model
  if(rt==CEntity::RT_MODEL || rt==CEntity::RT_EDITORMODEL) {
    ASSERT(en_pmoModelObject != NULL);
  // is this ska model
  } else if(rt==CEntity::RT_SKAMODEL || rt==CEntity::RT_SKAEDITORMODEL) {
    ASSERT(GetModelInstance() != NULL);
  } else {
    return;
  }
  // if( rt!=CEntity::RT_MODEL && rt!=CEntity::RT_EDITORMODEL) return;
  // ASSERT(en_pmoModelObject != NULL);

  CPlacement3D plPlacement = GetPlacement();
  FLOAT3D vRay[4];
  // if it is movable entity
  if( en_ulPhysicsFlags & EPF_MOVABLE) {
    INDEX iEq;
    FLOATaabbox3D box;
    GetCollisionBoxParameters(GetCollisionBoxIndex(), box, iEq);
    FLOAT3D vMin = box.Min();
    FLOAT3D vMax = box.Max();
    // all ray casts start from same height
    vRay[0](2) = vMax(2);
    vRay[1](2) = vMax(2);
    vRay[2](2) = vMax(2);
    vRay[3](2) = vMax(2);

    vRay[0](1) = vMin(1);
    vRay[0](3) = vMin(3);
    vRay[1](1) = vMin(1);
    vRay[1](3) = vMax(3);
    vRay[2](1) = vMax(1);
    vRay[2](3) = vMin(3);
    vRay[3](1) = vMax(1);
    vRay[3](3) = vMax(3);
  }
  else {
    FLOATaabbox3D box;
    if(rt==CEntity::RT_SKAMODEL || rt==CEntity::RT_SKAEDITORMODEL) {
      GetModelInstance()->GetCurrentColisionBox( box);
    } else {
      en_pmoModelObject->GetCurrentFrameBBox( box);
    }

    FLOAT3D vCenterUp = box.Center();
    vCenterUp(2) = box.Max()(2);
    vRay[0] = vCenterUp;
    vRay[1] = vCenterUp;
    vRay[2] = vCenterUp;
    vRay[3] = vCenterUp;
  }

  FLOAT fMaxY = -9999999.0f;
  BOOL bFloorHitted = FALSE;
  for( INDEX iRay=0; iRay<4; iRay++)
  {
    FLOAT3D vSource = plPlacement.pl_PositionVector+vRay[iRay];
    FLOAT3D vTarget = vSource;
    vTarget(2) -= 1000.0f;
    CCastRay crRay( this, vSource, vTarget);
    crRay.cr_ttHitModels = CCastRay::TT_NONE; // CCastRay::TT_FULLSEETHROUGH;
    crRay.cr_bHitTranslucentPortals = TRUE;
    crRay.cr_bPhysical = TRUE;
    GetWorld()->CastRay(crRay);
    if( (crRay.cr_penHit != NULL) && (crRay.cr_vHit(2) > fMaxY)) {
      fMaxY = crRay.cr_vHit(2);
      bFloorHitted = TRUE;
    }
  }
  if( bFloorHitted) plPlacement.pl_PositionVector(2) += fMaxY-plPlacement.pl_PositionVector(2)+0.01f;
  SetPlacement( plPlacement);
}


extern CEntity *_penLightUpdating;
BOOL _bDontDiscardLinks = FALSE;

// internal repositioning function
void CEntity::SetPlacement_internal(const CPlacement3D &plNew, const FLOATmatrix3D &mRotation,
   BOOL bNear)
{
  ASSERT(GetFPUPrecision()==FPT_24BIT);
  _pfPhysicsProfile.StartTimer(CPhysicsProfile::PTI_SETPLACEMENT);
  _pfPhysicsProfile.IncrementTimerAveragingCounter(CPhysicsProfile::PTI_SETPLACEMENT);

  // invalidate eventual cached info for still models
  en_ulFlags &= ~ENF_VALIDSHADINGINFO;

  _pfPhysicsProfile.StartTimer(CPhysicsProfile::PTI_SETPLACEMENT_COORDSUPDATE);
  // remembel old placement of the entity
  CPlacement3D plOld = en_plPlacement;
  // set new placement of the entity
  en_plPlacement = plNew;
  en_mRotation = mRotation;
  _pfPhysicsProfile.StopTimer(CPhysicsProfile::PTI_SETPLACEMENT_COORDSUPDATE);

  // if this is a brush entity
  if (en_RenderType==RT_BRUSH || en_RenderType==RT_FIELDBRUSH) {
    _pfPhysicsProfile.StartTimer(CPhysicsProfile::PTI_SETPLACEMENT_BRUSHUPDATE);
    // recalculate all bounding boxes relative to new position
    _bDontDiscardLinks = TRUE;
    en_pbrBrush->CalculateBoundingBoxes();
    _bDontDiscardLinks = FALSE;

    BOOL bHasShadows=FALSE;
    // for all brush mips
    FOREACHINLIST(CBrushMip, bm_lnInBrush, en_pbrBrush->br_lhBrushMips, itbm) {
      // for all sectors in the mip
      {FOREACHINDYNAMICARRAY(itbm->bm_abscSectors, CBrushSector, itbsc) {
        // for all polygons in this sector
        {FOREACHINSTATICARRAY(itbsc->bsc_abpoPolygons, CBrushPolygon, itbpo) {
          // if the polygon has shadows
          if (!(itbpo->bpo_ulFlags & BPOF_FULLBRIGHT)) {
            // discard shadows
            itbpo->DiscardShadows();
            bHasShadows = TRUE;
          }
        }}
      }}
    }
    // find possible shadow layers near affected area
    if (bHasShadows) {
      if (en_ulFlags&ENF_DYNAMICSHADOWS) {
        _penLightUpdating = NULL;
      } else {
        _penLightUpdating = this;
      }
      en_pwoWorld->FindShadowLayers(en_pbrBrush->GetFirstMip()->bm_boxBoundingBox,
        FALSE, FALSE /* no directional */);
      _penLightUpdating = NULL;
    }

    // if it is zoning
    if (en_ulFlags&ENF_ZONING) {
      // FPU must be in 53-bit mode
      CSetFPUPrecision FPUPrecision(FPT_53BIT);

      // for all brush mips
      FOREACHINLIST(CBrushMip, bm_lnInBrush, en_pbrBrush->br_lhBrushMips, itbm) {
        // for all sectors in the mip
        {FOREACHINDYNAMICARRAY(itbm->bm_abscSectors, CBrushSector, itbsc) {
          // find entities in sector
          itbsc->FindEntitiesInSector();
        }}
      }
    }

    _pfPhysicsProfile.StopTimer(CPhysicsProfile::PTI_SETPLACEMENT_BRUSHUPDATE);
  } else if(en_RenderType==RT_TERRAIN) {
    // Update terrain shadow map
    CTerrain *ptrTerrain = GetTerrain();
    ASSERT(ptrTerrain!=NULL);
    ptrTerrain->UpdateShadowMap();
  }

  // set spatial clasification
  _pfPhysicsProfile.StartTimer(CPhysicsProfile::PTI_SETPLACEMENT_SPATIALUPDATE);
  if (bNear) {
    FindSectorsAroundEntityNear();
  } else {
    FindSectorsAroundEntity();
  }
  _pfPhysicsProfile.StopTimer(CPhysicsProfile::PTI_SETPLACEMENT_SPATIALUPDATE);

  _pfPhysicsProfile.StartTimer(CPhysicsProfile::PTI_SETPLACEMENT_LIGHTUPDATE);
  // if it is a light source
  {CLightSource *pls = GetLightSource();
  if (pls!=NULL) {
    // find all shadow maps that should have layers from this light source
    pls->FindShadowLayers(FALSE);
    // update shadow map on all terrains in world
    pls->UpdateTerrains(plOld,en_plPlacement);
  }}

  _pfPhysicsProfile.StopTimer(CPhysicsProfile::PTI_SETPLACEMENT_LIGHTUPDATE);

  // move the entity to new position in collision grid
  if (en_pciCollisionInfo!=NULL) {
    _pfPhysicsProfile.StartTimer(CPhysicsProfile::PTI_SETPLACEMENT_COLLISIONUPDATE);
    FLOATaabbox3D boxNew;
    en_pciCollisionInfo->MakeBoxAtPlacement(
      en_plPlacement.pl_PositionVector, en_mRotation, boxNew);
    if (en_RenderType!=RT_BRUSH && en_RenderType!=RT_FIELDBRUSH) {
      en_pwoWorld->MoveEntityInCollisionGrid( this, en_pciCollisionInfo->ci_boxCurrent, boxNew);
    }
    en_pciCollisionInfo->ci_boxCurrent = boxNew;
    _pfPhysicsProfile.StopTimer(CPhysicsProfile::PTI_SETPLACEMENT_COLLISIONUPDATE);
  }

  _pfPhysicsProfile.StopTimer(CPhysicsProfile::PTI_SETPLACEMENT);

  // NOTE: this is outside profile because it uses recursion

  // for each child of this entity
  {FOREACHINLIST(CEntity, en_lnInParent, en_lhChildren, itenChild) {
    CPlacement3D plNew = itenChild->en_plRelativeToParent;
    plNew.RelativeToAbsoluteSmooth(en_plPlacement);
    itenChild->SetPlacement(plNew);
  }}
}
// this one is used in rendering - gets lerped placement between ticks
CPlacement3D CEntity::GetLerpedPlacement(void) const
{
  // if it has no parent
  if (en_penParent==NULL) {
    // no lerping
    return en_plPlacement;
  // if it has parent
  } else {
    // get lerped placement relative to parent
    CPlacement3D plParentLerped = en_penParent->GetLerpedPlacement();
    CPlacement3D plLerped = en_plRelativeToParent;
    plLerped.RelativeToAbsoluteSmooth(plParentLerped);
    return plLerped;
  }
}

void CEntity::SetFlags(ULONG ulFlags)
{
  en_ulFlags = ulFlags;
}

void CEntity::SetPhysicsFlags(ULONG ulFlags)
{
  // remember the new flags
  en_ulPhysicsFlags = ulFlags;

  // cache eventual collision info
  FindCollisionInfo();
}

void CEntity::SetCollisionFlags(ULONG ulFlags)
{
  // remember the new flags
  en_ulCollisionFlags = ulFlags;

  // cache eventual collision info
  FindCollisionInfo();
}

void CEntity::SetParent(CEntity *penNewParent)
{
  // if there is a parent already
  if (en_penParent!=NULL) {
    // remove from it
    en_penParent = NULL;
    en_lnInParent.Remove();
  }

  // if should set new parent
  if (penNewParent!=NULL) {
    // for each predecesor (parent) entity in the chain
    for (CEntity *penPred=penNewParent; penPred!=NULL; penPred=penPred->en_penParent) {
      // if self
      if (penPred==this) {
        // refuse to set parent
        return;
      }
    }

    // set new parent
    en_penParent = penNewParent;
    penNewParent->en_lhChildren.AddTail(en_lnInParent);
    // calculate relative placement
    en_plRelativeToParent = en_plPlacement;
    en_plRelativeToParent.AbsoluteToRelativeSmooth(en_penParent->en_plPlacement);
  }
}


// find first child of given class
CEntity *CEntity::GetChildOfClass(const char *strClass)
{
  // for each child of this entity
  {FOREACHINLIST(CEntity, en_lnInParent, en_lhChildren, itenChild) {
    // if it is of given class
    if (IsOfClass(itenChild, strClass)) {
      return itenChild;
    }
  }}
  // not found
  return NULL;
}  

/*
 * Destroy this entity (entity must not be targetable).
 */
void CEntity::Destroy(void)
{
  ASSERT(GetFPUPrecision()==FPT_24BIT);

  // if it is already destroyed
  if (en_ulFlags&ENF_DELETED) {
    // do nothing
    return;
  }
  // if it is a light source
  {CLightSource *pls = GetLightSource();
  if (pls!=NULL) {
    // destroy all of its shadow layers
    pls->DiscardShadowLayers();
  }}
  // clean up the entity
  End();
  SetDefaultProperties(); // this effectively clears all entity pointers!

  // unlink parent-child links
  if (en_penParent != NULL) {
    en_penParent = NULL;
    en_lnInParent.Remove();
  }
  {FORDELETELIST( CEntity, en_lnInParent, en_lhChildren, itenChild) {
    itenChild->en_penParent = NULL;
    itenChild->en_lnInParent.Remove();
  }}

  // set its flags to mark that it doesn't not exist anymore
  en_ulFlags|=ENF_DELETED;
  // make sure that no deleted entity can be alive
  en_ulFlags&=~ENF_ALIVE;
  // remove from all sectors
  en_rdSectors.Clear();
  // remove from active entities in the world
  en_pwoWorld->wo_cenEntities.Remove(this);
  // remove the reference made by the entity itself (this can delete it!)
  RemReference();
}


FLOAT3D _vHandle;
CBrushPolygon *_pbpoNear;
CTerrain *_ptrTerrainNear;
FLOAT _fNearDistance;
FLOAT3D _vNearPoint;

static void CheckPolygonForShadingInfo(CBrushPolygon &bpo)
{
  // if it is not a wall
  if (bpo.bpo_ulFlags&(BPOF_INVISIBLE|BPOF_PORTAL) ) {
    // skip it
    return;
  }
  // if the polygon or it's entity are invisible
  if (bpo.bpo_pbscSector->bsc_pbmBrushMip->bm_pbrBrush->br_penEntity->en_ulFlags&ENF_HIDDEN) {
    // skip it
    return;
  }

  const FLOATplane3D &plPolygon = bpo.bpo_pbplPlane->bpl_plAbsolute;
  // find distance of the polygon plane from the handle
  FLOAT fDistance = plPolygon.PointDistance(_vHandle);
  // if it is behind the plane or further than nearest found
  if (fDistance<0.0f || fDistance>_fNearDistance) {
    // skip it
    return;
  }
  // find projection of handle to the polygon plane
  FLOAT3D vOnPlane = plPolygon.ProjectPoint(_vHandle);
  // if it is not in the bounding box of polygon
  const FLOATaabbox3D &boxPolygon = bpo.bpo_boxBoundingBox;
  const FLOAT EPSILON = 0.01f;
  if (
    (boxPolygon.Min()(1)-EPSILON>vOnPlane(1)) ||
    (boxPolygon.Max()(1)+EPSILON<vOnPlane(1)) ||
    (boxPolygon.Min()(2)-EPSILON>vOnPlane(2)) ||
    (boxPolygon.Max()(2)+EPSILON<vOnPlane(2)) ||
    (boxPolygon.Min()(3)-EPSILON>vOnPlane(3)) ||
    (boxPolygon.Max()(3)+EPSILON<vOnPlane(3))) {
    // skip it
    return;
  }

  // find major axes of the polygon plane
  INDEX iMajorAxis1, iMajorAxis2;
  GetMajorAxesForPlane(plPolygon, iMajorAxis1, iMajorAxis2);

  // create an intersector
  CIntersector isIntersector(_vHandle(iMajorAxis1), _vHandle(iMajorAxis2));
  // for all edges in the polygon
  FOREACHINSTATICARRAY(bpo.bpo_abpePolygonEdges, CBrushPolygonEdge, itbpePolygonEdge) {
    // get edge vertices (edge direction is irrelevant here!)
    const FLOAT3D &vVertex0 = itbpePolygonEdge->bpe_pbedEdge->bed_pbvxVertex0->bvx_vAbsolute;
    const FLOAT3D &vVertex1 = itbpePolygonEdge->bpe_pbedEdge->bed_pbvxVertex1->bvx_vAbsolute;
    // pass the edge to the intersector
    isIntersector.AddEdge(
      vVertex0(iMajorAxis1), vVertex0(iMajorAxis2),
      vVertex1(iMajorAxis1), vVertex1(iMajorAxis2));
  }

  // if the point is not inside polygon
  if (!isIntersector.IsIntersecting()) {
    // skip it
    return;
  }

  // remember the polygon
  _pbpoNear = &bpo;
  _fNearDistance = fDistance;
  _vNearPoint = vOnPlane;
}

static void CheckTerrainForShadingInfo(CTerrain *ptrTerrain)
{
  ASSERT(ptrTerrain!=NULL);
  ASSERT(ptrTerrain->tr_penEntity!=NULL);
  CEntity *pen = ptrTerrain->tr_penEntity;

  FLOAT3D vTerrainNormal;
  FLOAT3D vHitPoint;
  FLOATplane3D plHitPlane;
  vTerrainNormal = FLOAT3D(0,-1,0) * pen->en_mRotation;
  FLOAT fDistance = TestRayCastHit(ptrTerrain,pen->en_mRotation,pen->en_plPlacement.pl_PositionVector,
                                   _vHandle,_vHandle+vTerrainNormal,_fNearDistance,FALSE,plHitPlane,vHitPoint);
  if(fDistance<_fNearDistance) {
    _vNearPoint = vHitPoint;
    _fNearDistance = fDistance;
    _ptrTerrainNear = ptrTerrain;
  }
}

/* Find and remember shading info for this entity if invalid. */
void CEntity::FindShadingInfo(void)
{
  ASSERT(GetFPUPrecision()==FPT_24BIT);

  // if this entity can't even have shading info
  if (en_psiShadingInfo==NULL) {
    // do nothing
    return;
  }

  // if info is valid
  if (en_ulFlags & ENF_VALIDSHADINGINFO) {
    // !!! check if the polygon is still there !
    // do nothing
    return;
  }
  en_ulFlags |= ENF_VALIDSHADINGINFO;

  en_psiShadingInfo->si_penEntity = this;

  // clear shading info
  en_psiShadingInfo->si_pbpoPolygon = NULL;
  en_psiShadingInfo->si_ptrTerrain  = NULL;
  if (en_psiShadingInfo->si_lnInPolygon.IsLinked()) {
    en_psiShadingInfo->si_lnInPolygon.Remove();
  }

  // take reference point at handle of the model entity
  _vHandle = en_plPlacement.pl_PositionVector;
  // start infinitely far away
  _pbpoNear = NULL;
  _ptrTerrainNear = NULL;
  _fNearDistance = UpperLimit(1.0f);
  // if this is movable entity
  if (en_ulPhysicsFlags&EPF_MOVABLE) {
    // for each cached near polygon
    CStaticStackArray<CBrushPolygon *> &apbpo = ((CMovableEntity*)this)->en_apbpoNearPolygons;
    for(INDEX iPolygon=0; iPolygon<apbpo.Count(); iPolygon++) {
      CheckPolygonForShadingInfo(*apbpo[iPolygon]);
    }
  }

  // for each sector that this entity is in
  {FOREACHSRCOFDST(en_rdSectors, CBrushSector, bsc_rsEntities, pbsc)
    // for each brush or terrain in this sector
    {FOREACHDSTOFSRC(pbsc->bsc_rsEntities, CEntity, en_rdSectors, pen)
      if(pen->en_RenderType==CEntity::RT_TERRAIN) {
        CheckTerrainForShadingInfo(pen->GetTerrain());
      } else if(pen->en_RenderType!=CEntity::RT_BRUSH && pen->en_RenderType!=CEntity::RT_FIELDBRUSH) {
        break;
      }
    }}
  ENDFOR}

  // if this is non-movable entity, or no polygon or terrain found so far
  if (_pbpoNear==NULL && _ptrTerrainNear==NULL) {
    // for each sector that this entity is in
    {FOREACHSRCOFDST(en_rdSectors, CBrushSector, bsc_rsEntities, pbsc)
      // for each polygon in the sector
      {FOREACHINSTATICARRAY(pbsc->bsc_abpoPolygons, CBrushPolygon, itbpo) {
        CBrushPolygon &bpo = *itbpo;
        CheckPolygonForShadingInfo(bpo);
      }}
    ENDFOR}
  }

  // if there is some polygon found
  if( _pbpoNear!=NULL) {
    // remember shading info
    en_psiShadingInfo->si_pbpoPolygon = _pbpoNear;
    _pbpoNear->bpo_lhShadingInfos.AddTail(en_psiShadingInfo->si_lnInPolygon);
    en_psiShadingInfo->si_vNearPoint = _vNearPoint;

    CEntity *penWithPolygon = _pbpoNear->bpo_pbscSector->bsc_pbmBrushMip->bm_pbrBrush->br_penEntity;
    ASSERT(penWithPolygon!=NULL);
    const FLOATmatrix3D &mPolygonRotation = penWithPolygon->en_mRotation;
    const FLOAT3D &vPolygonTranslation = penWithPolygon->GetPlacement().pl_PositionVector;

    _vNearPoint = (_vNearPoint-vPolygonTranslation)*!mPolygonRotation;

    MEX2D vmexShadow;
    _pbpoNear->bpo_mdShadow.GetTextureCoordinates(
      _pbpoNear->bpo_pbplPlane->bpl_pwplWorking->wpl_mvRelative,
      _vNearPoint, vmexShadow);
    CBrushShadowMap &bsm = _pbpoNear->bpo_smShadowMap;
    INDEX iMipLevel = bsm.sm_iFirstMipLevel;
    FLOAT fpixU = FLOAT(vmexShadow(1)+bsm.sm_mexOffsetX)*(1.0f/(1<<iMipLevel));
    FLOAT fpixV = FLOAT(vmexShadow(2)+bsm.sm_mexOffsetY)*(1.0f/(1<<iMipLevel));
    en_psiShadingInfo->si_pixShadowU = (PIX) floor(fpixU);
    en_psiShadingInfo->si_pixShadowV = (PIX) floor(fpixV);
    en_psiShadingInfo->si_fUDRatio = fpixU-en_psiShadingInfo->si_pixShadowU;
    en_psiShadingInfo->si_fLRRatio = fpixV-en_psiShadingInfo->si_pixShadowV;

  // else if there is some terrain found
  } else if(_ptrTerrainNear!=NULL) {
    // remember shading info
    en_psiShadingInfo->si_ptrTerrain = _ptrTerrainNear;
    en_psiShadingInfo->si_vNearPoint = _vNearPoint;
    
    FLOAT2D vTc = CalculateShadingTexCoords(_ptrTerrainNear,_vNearPoint);
    en_psiShadingInfo->si_pixShadowU = (PIX) floor(vTc(1));
    en_psiShadingInfo->si_pixShadowV = (PIX) floor(vTc(2));
    en_psiShadingInfo->si_fLRRatio   = vTc(1) - en_psiShadingInfo->si_pixShadowU;
    en_psiShadingInfo->si_fUDRatio   = vTc(2) - en_psiShadingInfo->si_pixShadowV;

    _ptrTerrainNear->tr_lhShadingInfos.AddTail(en_psiShadingInfo->si_lnInPolygon);
  }
}

CBrushSector *CEntity::GetFirstSector(void)
{
  {FOREACHSRCOFDST(en_rdSectors, CBrushSector, bsc_rsEntities, pbsc)
    return pbsc;
  ENDFOR};
  return NULL;
}

CBrushSector *CEntity::GetFirstSectorWithName(void)
{
  CBrushSector *pbscResult = NULL;
  {FOREACHSRCOFDST(en_rdSectors, CBrushSector, bsc_rsEntities, pbsc)
    if (pbsc->bsc_strName!="") {
      pbscResult = pbsc;
      break;
    }
  ENDFOR};
  return pbscResult;
}

// max. distance between two spheres (as factor of radius of one sphere)
#define MIN_SPHEREDENSITY 1.0f

CCollisionInfo::CCollisionInfo(const CCollisionInfo &ciOrg)
{
  ci_absSpheres = ciOrg.ci_absSpheres ;
  ci_fMinHeight = ciOrg.ci_fMinHeight ;
  ci_fMaxHeight = ciOrg.ci_fMaxHeight ;
  ci_fHandleY   = ciOrg.ci_fHandleY   ;
  ci_fHandleR   = ciOrg.ci_fHandleR   ;
  ci_boxCurrent = ciOrg.ci_boxCurrent ;
  ci_ulFlags    = ciOrg.ci_ulFlags    ;
}
/* Create collision info for a model. */
void CCollisionInfo::FromModel(CEntity *penModel, INDEX iBox)
{
  ASSERT(GetFPUPrecision()==FPT_24BIT);

  // get collision box information from the model
  FLOATaabbox3D boxModel;
  INDEX iBoxType;
  penModel->GetCollisionBoxParameters(iBox, boxModel, iBoxType);
  FLOAT3D vBoxOffset = boxModel.Center();
  FLOAT3D vBoxSize = boxModel.Size();

//  ASSERT(iBoxType==LENGHT_EQ_WIDTH);
  ci_ulFlags = 0;

  INDEX iAxisMain; // in which direction are spheres set
  INDEX iAxis1, iAxis2; // other axis

  if (iBoxType==LENGTH_EQ_WIDTH) {
    iAxisMain = 2;
    iAxis1 = 1; iAxis2 = 3;
  } else if (iBoxType==HEIGHT_EQ_WIDTH) {
    iAxisMain = 3;
    iAxis1 = 2; iAxis2 = 1;
  } else if (iBoxType==LENGTH_EQ_HEIGHT) {
    iAxisMain = 1;
    iAxis1 = 2; iAxis2 = 3;
  } else {
    ASSERTALWAYS("Invalid collision box");
    iAxisMain = 2;
    iAxis1 = 1; iAxis2 = 3;
  }

  // calculate radius of one sphere
  FLOAT fSphereRadius = vBoxSize(iAxis1)/2.0f;
  // calculate length along which to set spheres
  FLOAT fSphereCentersSpan = vBoxSize(iAxisMain)-fSphereRadius*2;
  // calculate number of spheres to use
  INDEX ctSpheres = 0;
  if (fSphereRadius>0.0001f) {
    ctSpheres = INDEX(ceil(fSphereCentersSpan/(fSphereRadius*MIN_SPHEREDENSITY)))+1;
  }
  if (ctSpheres==0) {
    ctSpheres=1;
  }
  // calculate how far from each other to set sphere centers
  FLOAT fSphereCentersDistance;
  if (ctSpheres==1) {
    fSphereCentersDistance = 0.0f;
  } else {
    fSphereCentersDistance = fSphereCentersSpan/(FLOAT)(ctSpheres-1);
  }

  // calculate coordinates for spreading sphere centers
  FLOAT fSphereCenterX = vBoxOffset(iAxis1);
  FLOAT fSphereCenterZ = vBoxOffset(iAxis2);
  FLOAT fSphereCenterY0 = vBoxOffset(iAxisMain)-(vBoxSize(iAxisMain)/2.0f)+fSphereRadius;
  FLOAT fSphereCenterKY = fSphereCentersDistance;

  ci_fMinHeight = boxModel.Min()(2);
  ci_fMaxHeight = boxModel.Max()(2);
  ci_fHandleY = UpperLimit(1.0f);
  // create needed number of spheres in the array
  ci_absSpheres.Clear();
  ci_absSpheres.New(ctSpheres);
  // for each sphere
  for(INDEX iSphere=0; iSphere<ctSpheres; iSphere++) {
    CMovingSphere &ms = ci_absSpheres[iSphere];
    // set its center and radius
    ms.ms_vCenter(iAxis1) = fSphereCenterX;
    ms.ms_vCenter(iAxis2) = fSphereCenterZ;
    ms.ms_vCenter(iAxisMain) = fSphereCenterY0+iSphere*fSphereCenterKY;
    ms.ms_fR = fSphereRadius;
    ci_fHandleY = Min(ci_fHandleY, ms.ms_vCenter(2));
  }

  // remember handle parameters
  if (ctSpheres==1 || iBoxType==LENGTH_EQ_WIDTH) {
    ci_ulFlags|=CIF_CANSTANDONHANDLE;
    ci_fHandleR = fSphereRadius;
  } else {
    ci_fHandleR = 0.0f;
  }

  // set optimization flags

  if (ctSpheres==1 &&
    ci_absSpheres[0].ms_vCenter(1)==0 &&
    ci_absSpheres[0].ms_vCenter(2)==0 &&
    ci_absSpheres[0].ms_vCenter(3)==0) {
    ci_ulFlags|=CIF_IGNOREROTATION;
  }

  if (iBoxType==LENGTH_EQ_WIDTH &&
    ci_absSpheres[0].ms_vCenter(1)==0 &&
    ci_absSpheres[0].ms_vCenter(3)==0) {
    ci_ulFlags|=CIF_IGNOREHEADING;
  }
}

/* Create collision info for a ska model */
void CCollisionInfo::FromSkaModel(CEntity *penModel, INDEX iBox)
{
}
/* Create collision info for a brush. */
void CCollisionInfo::FromBrush(CBrush3D *pbrBrush)
{
  ASSERT(GetFPUPrecision()==FPT_24BIT);

  ci_absSpheres.Clear();
  ci_absSpheres.New(1);
  ci_ulFlags = CIF_BRUSH;
  // clear brush's relative box
  FLOATaabbox3D box;
  // get first brush mip
  CBrushMip *pbm = pbrBrush->GetFirstMip();
  // for each sector in the brush mip
  {FOREACHINDYNAMICARRAY(pbm->bm_abscSectors, CBrushSector, itbsc) {
    // for each vertex in the sector
    {FOREACHINSTATICARRAY(itbsc->bsc_abvxVertices, CBrushVertex, itbvx) {
      CBrushVertex &bvx = *itbvx;
      // add it to bounding box
      box |= DOUBLEtoFLOAT(bvx.bvx_vdPreciseRelative);
    }}
  }}

  // create a sphere from the relative box
  ci_absSpheres[0].ms_vCenter = box.Center();
  ci_absSpheres[0].ms_fR = box.Size().Length()/2;
  ci_fMinHeight = UpperLimit(1.0f);
  ci_fMaxHeight = LowerLimit(1.0f);
  ci_fHandleY   = 0.0f;
  ci_fHandleR   = 1.0f;
}

/* Calculate current bounding box in absolute space from position. */
void CCollisionInfo::MakeBoxAtPlacement(const FLOAT3D &vPosition, const FLOATmatrix3D &mRotation,
  FLOATaabbox3D &box)
{
  ASSERT(GetFPUPrecision()==FPT_24BIT);

  CMovingSphere &ms0 = ci_absSpheres[0];
  CMovingSphere &ms1 = ci_absSpheres[ci_absSpheres.Count()-1];
  box  = FLOATaabbox3D(vPosition+ms0.ms_vCenter*mRotation, ms0.ms_fR);
  box |= FLOATaabbox3D(vPosition+ms1.ms_vCenter*mRotation, ms1.ms_fR);
}

// get maximum radius of entity in xz plane (relative to entity handle)
FLOAT CCollisionInfo::GetMaxFloorRadius(void)
{
  ASSERT(GetFPUPrecision()==FPT_24BIT);

  // get first and last sphere
  CMovingSphere &ms0 = ci_absSpheres[0];
  CMovingSphere &ms1 = ci_absSpheres[ci_absSpheres.Count()-1];
  // get their positions in xz plane
  FLOAT3D vPosXZ0 = ms0.ms_vCenter;
  FLOAT3D vPosXZ1 = ms1.ms_vCenter;
  vPosXZ0(2) = 0.0f;
  vPosXZ1(2) = 0.0f;
  // return maximum distance from the handle in xz plane
  return Max(
    vPosXZ0.Length()+ms0.ms_fR, 
    vPosXZ1.Length()+ms1.ms_fR);
}


/* Find and remember collision info for this entity. */
void CEntity::FindCollisionInfo(void)
{
  ASSERT(GetFPUPrecision()==FPT_24BIT);

  // discard eventual collision info
  DiscardCollisionInfo();

  // if the entity is colliding
  if (en_ulCollisionFlags&ECF_TESTMASK) {
    // if it is a model
    if ((en_RenderType==RT_MODEL||en_RenderType==RT_EDITORMODEL)
    &&(en_pmoModelObject->GetData()!=NULL)) {
      // cache its new collision info
      en_pciCollisionInfo = new CCollisionInfo;
      en_pciCollisionInfo->FromModel(this, GetCollisionBoxIndex());
    } else if ((en_RenderType==RT_SKAMODEL||en_RenderType==RT_SKAEDITORMODEL)
      &&(GetModelInstance()!=NULL)) {
      // cache its new collision info
      en_pciCollisionInfo = new CCollisionInfo;
      en_pciCollisionInfo->FromModel(this, GetCollisionBoxIndex());
    // if it is a brush
    } else if (en_RenderType==RT_BRUSH) {
      // if it is zoning brush and non movable
      if ((en_ulFlags&ENF_ZONING) && !(en_ulPhysicsFlags&EPF_MOVABLE)) {
        // do nothing
        return;
      }
      // cache its new collision info
      en_pciCollisionInfo = new CCollisionInfo;
      en_pciCollisionInfo->FromBrush(en_pbrBrush);
    // if it is a field brush
    } else if (en_RenderType==RT_FIELDBRUSH) {
      // cache its new collision info
      en_pciCollisionInfo = new CCollisionInfo;
      en_pciCollisionInfo->FromBrush(en_pbrBrush);
      return;
    } else if (en_RenderType==RT_TERRAIN) {
      return;
    } else {
      return;
    }
    // add entity to collision grid
    FLOATaabbox3D boxNew;
    en_pciCollisionInfo->MakeBoxAtPlacement(
      en_plPlacement.pl_PositionVector, en_mRotation, boxNew);
    if (en_RenderType!=RT_BRUSH && en_RenderType!=RT_FIELDBRUSH) {
      en_pwoWorld->AddEntityToCollisionGrid(this, boxNew);
    }
    en_pciCollisionInfo->ci_boxCurrent = boxNew;
  }
}
// discard collision info for this entity
void CEntity::DiscardCollisionInfo(void)
{
  // if there was any collision info
  if (en_pciCollisionInfo!=NULL) {
    // remove entity from collision grid
    if (en_RenderType!=RT_BRUSH && en_RenderType!=RT_FIELDBRUSH) {
      en_pwoWorld->RemoveEntityFromCollisionGrid(this, en_pciCollisionInfo->ci_boxCurrent);
    }
    // free it
    delete en_pciCollisionInfo;
    en_pciCollisionInfo = NULL;
  }
  // movable entity
  if (en_ulPhysicsFlags & EPF_MOVABLE) {
    ((CMovableEntity*)this)->ClearTemporaryData();
  }
}
// copy collision info from some other entity
void CEntity::CopyCollisionInfo(CEntity &enOrg)
{
  ASSERT(GetFPUPrecision()==FPT_24BIT);

  // if there is no collision info
  if (enOrg.en_pciCollisionInfo==NULL) {
    // do nothing
    en_pciCollisionInfo = NULL;
    return;
  }
  // create info and copy it
  en_pciCollisionInfo = new CCollisionInfo(*enOrg.en_pciCollisionInfo);
  // add entity to collision grid
  FLOATaabbox3D boxNew;
  en_pciCollisionInfo->MakeBoxAtPlacement(
    en_plPlacement.pl_PositionVector, en_mRotation, boxNew);
  if (en_RenderType!=RT_BRUSH && en_RenderType!=RT_FIELDBRUSH) {
    en_pwoWorld->AddEntityToCollisionGrid(this, boxNew);
  }
  en_pciCollisionInfo->ci_boxCurrent = boxNew;
}

/* Get box and sphere for spatial clasification. */
void CEntity::UpdateSpatialRange(void)
{
  CSetFPUPrecision FPUPrecision(FPT_24BIT);

  en_fSpatialClassificationRadius = -1.0f;

  // if zoning
  if (en_ulFlags&ENF_ZONING) {
    // do nothing
    return;
  }

  FLOATaabbox3D box;
  FLOATaabbox3D boxStretched;
  // get bounding box of the entity
  // is this old model
  if (en_RenderType==CEntity::RT_MODEL
    ||en_RenderType==CEntity::RT_EDITORMODEL) {
    en_pmoModelObject->GetAllFramesBBox(box);
    box.StretchByVector(en_pmoModelObject->mo_Stretch);
    FLOAT3D fClassificationStretch = GetClassificationBoxStretch();
    boxStretched = box;
    boxStretched .StretchByVector( fClassificationStretch);
    en_boxSpatialClassification = boxStretched;
  // is this ska model
  } else if (en_RenderType==CEntity::RT_SKAMODEL
    || en_RenderType==RT_SKAEDITORMODEL) {
    GetModelInstance()->GetAllFramesBBox(box);
    box.StretchByVector(GetModelInstance()->mi_vStretch);
    FLOAT3D fClassificationStretch = GetClassificationBoxStretch();
    boxStretched = box;
    boxStretched.StretchByVector( fClassificationStretch);
    en_boxSpatialClassification = boxStretched;
  // is this brush
  } else if (en_RenderType==CEntity::RT_BRUSH || en_RenderType==RT_FIELDBRUSH) {
    box = en_pbrBrush->GetFirstMip()->bm_boxRelative;
    boxStretched = box;
    en_boxSpatialClassification = box;
  // is this terrain
  } else if (en_RenderType==CEntity::RT_TERRAIN) {
    GetTerrain()->GetAllTerrainBBox(box);
    boxStretched = box;
    en_boxSpatialClassification = box;
  } else {
    return; // sound entities are not related to sectors !!!!
  }
  en_fSpatialClassificationRadius = Max( box.Min().Length(), box.Max().Length() );
  ASSERT(IsValidFloat(en_fSpatialClassificationRadius));
}

/* Find and remember all sectors that this entity is in. */
void CEntity::FindSectorsAroundEntity(void)
{
  CSetFPUPrecision sfp(FPT_53BIT);

  // if not in spatial clasification
  if (en_fSpatialClassificationRadius<0) {
    // do nothing
    return;
  }
  // get bounding sphere and box of entity
  FLOAT fSphereRadius = en_fSpatialClassificationRadius;
  const FLOAT3D &vSphereCenter = en_plPlacement.pl_PositionVector;
  // make oriented bounding box of the entity
  FLOATobbox3D boxEntity = FLOATobbox3D(en_boxSpatialClassification, 
    en_plPlacement.pl_PositionVector, en_mRotation);

  // unset spatial clasification
  en_rdSectors.Clear();

  // for each brush in the world
  FOREACHINDYNAMICARRAY(en_pwoWorld->wo_baBrushes.ba_abrBrushes, CBrush3D, itbr) {
    //CBrush3D &br=*itbr;
    // if the brush entity is not zoning
    if (itbr->br_penEntity==NULL || !(itbr->br_penEntity->en_ulFlags&ENF_ZONING)) {
      // skip it
      continue;
    }
    // for each mip in the brush
    FOREACHINLIST(CBrushMip, bm_lnInBrush, itbr->br_lhBrushMips, itbm) {
      // for each sector in the brush mip
      FOREACHINDYNAMICARRAY(itbm->bm_abscSectors, CBrushSector, itbsc) {
        // if the sector's bounding box has contact with the sphere 
        if(itbsc->bsc_boxBoundingBox.TouchesSphere(vSphereCenter, fSphereRadius)
          // and with the box
          && boxEntity.HasContactWith(FLOATobbox3D(itbsc->bsc_boxBoundingBox))) {
          
          // if the sphere is inside the sector
          if (itbsc->bsc_bspBSPTree.TestSphere(
              vSphereCenter, fSphereRadius)>=0) {

            // if the box is inside the sector
            if (itbsc->bsc_bspBSPTree.TestBox(boxEntity)>=0) {
              // relate the entity to the sector
              if (en_RenderType==RT_BRUSH
                ||en_RenderType==RT_FIELDBRUSH
                ||en_RenderType==RT_TERRAIN) {  // brushes first
                AddRelationPairHeadHead(itbsc->bsc_rsEntities, en_rdSectors);
              } else {
                AddRelationPairTailTail(itbsc->bsc_rsEntities, en_rdSectors);
              }
            }
          }
        }
      }
    }
  }
}

void CEntity::FindSectorsAroundEntityNear(void)
{
  ASSERT(GetFPUPrecision()==FPT_24BIT);

  // if not in spatial clasification
  if (en_fSpatialClassificationRadius<0) {
    // do nothing
    return;
  }
  // this may be called only for movable entities
  ASSERT(en_ulPhysicsFlags&EPF_MOVABLE);
  CMovableEntity *pen = (CMovableEntity *)this;

  // get bounding sphere and box of entity
  FLOAT fSphereRadius = en_fSpatialClassificationRadius;
  const FLOAT3D &vSphereCenter = en_plPlacement.pl_PositionVector;
  FLOATaabbox3D boxEntity(vSphereCenter, fSphereRadius);
  // make oriented bounding box of the entity
  FLOATobbox3D oboxEntity = FLOATobbox3D(en_boxSpatialClassification, 
    en_plPlacement.pl_PositionVector, en_mRotation);

  CListHead lhActive;
  // for each sector around this entity
  {FOREACHSRCOFDST(en_rdSectors, CBrushSector, bsc_rsEntities, pbsc)
    // remember its link
    pbsc->bsc_prlLink = pbsc_iter;
    // add it to list of active sectors
    lhActive.AddTail(pbsc->bsc_lnInActiveSectors);
  ENDFOR}

  CStaticStackArray<CBrushPolygon *> &apbpo = pen->en_apbpoNearPolygons;
  // for each cached polygon
  for(INDEX iPolygon=0; iPolygon<apbpo.Count(); iPolygon++) {
    CBrushSector *pbsc = apbpo[iPolygon]->bpo_pbscSector;
    // add its sector if not already added, and has BSP (is zoning)
    if (!pbsc->bsc_lnInActiveSectors.IsLinked() && pbsc->bsc_bspBSPTree.bt_pbnRoot!=NULL) {
      lhActive.AddTail(pbsc->bsc_lnInActiveSectors);
      pbsc->bsc_prlLink = NULL;
    }
  }

  // for each active sector
  FOREACHINLIST(CBrushSector, bsc_lnInActiveSectors, lhActive, itbsc) {
    CBrushSector *pbsc = itbsc;
    // test if entity is in sector
    BOOL bIn =
        // the sector's bounding box has contact with given bounding box,
        (pbsc->bsc_boxBoundingBox.HasContactWith(boxEntity))&&
        // the sphere is inside the sector
        (pbsc->bsc_bspBSPTree.TestSphere(
              vSphereCenter, fSphereRadius)>=0)&&
        // (use more detailed testing for moving brushes)
        (en_RenderType!=RT_BRUSH||
          // oriented box touches box of sector
          ((oboxEntity.HasContactWith(FLOATobbox3D(pbsc->bsc_boxBoundingBox)))&&
          // oriented box is in bsp
          (pbsc->bsc_bspBSPTree.TestBox(oboxEntity)>=0)));
    // if it is not
    if (!bIn) {
      // if it has link
      if (pbsc->bsc_prlLink!=NULL) {
        // remove link to that sector
        delete pbsc->bsc_prlLink;
        pbsc->bsc_prlLink = NULL;
      }
    // if it is
    } else {
      // if it doesn't have link
      if (pbsc->bsc_prlLink==NULL) {
        // add the link
        if (en_RenderType==RT_BRUSH
          ||en_RenderType==RT_FIELDBRUSH
          ||en_RenderType==RT_TERRAIN) {  // brushes first
          AddRelationPairHeadHead(pbsc->bsc_rsEntities, en_rdSectors);
        } else {
          AddRelationPairTailTail(pbsc->bsc_rsEntities, en_rdSectors);
        }
      }
    }
  }

  // clear list of active sectors
  {FORDELETELIST(CBrushSector, bsc_lnInActiveSectors, lhActive, itbsc) {
    itbsc->bsc_prlLink = NULL;
    itbsc->bsc_lnInActiveSectors.Remove();
  }}
  ASSERT(lhActive.IsEmpty());

  // if there is no link found
  if (en_rdSectors.IsEmpty()) {
    // test with brute force algorithm
    FindSectorsAroundEntity();
  }
}

/*
 * Uncache shadows of each polygon that has given gradient index
 */
void CEntity::UncacheShadowsForGradient(INDEX iGradient)
{
  if(en_RenderType != CEntity::RT_BRUSH)
  {
    ASSERTALWAYS("Uncache shadows for gradient called on non-brush entity!");
    return;
  }

  // for all brush mips
  FOREACHINLIST(CBrushMip, bm_lnInBrush, en_pbrBrush->br_lhBrushMips, itbm)
  {
    // for all sectors in the mip
    {FOREACHINDYNAMICARRAY(itbm->bm_abscSectors, CBrushSector, itbsc)
    {
      // for all polygons in this sector
      {FOREACHINSTATICARRAY(itbsc->bsc_abpoPolygons, CBrushPolygon, itbpo)
      {
        // if the polygon has shadows
        if (itbpo->bpo_bppProperties.bpp_ubGradientType == iGradient)
        {
          // uncache shadows
          itbpo->bpo_smShadowMap.Uncache();
        }
      }}
    }}
  }
}

/*
 * Get state transition for given state and event code.
 */
CEntity::pEventHandler CEntity::HandlerForStateAndEvent(SLONG slState, SLONG slEvent)
{
  // find translation in the translation table of the DLL class
  return en_pecClass->HandlerForStateAndEvent(slState, slEvent);
}

/*
 * Handle an event - return false if event was not handled.
 */
BOOL CEntity::HandleEvent(const CEntityEvent &ee)
{
  /*
   By default, base entities ignore all events.
   Events are handled by classes derived from CRationalEntity using state stack.

   Anyway, it is possible to override this in some class if some other way
   of event handling is desired.
   */

  return FALSE;
}

/////////////////////////////////////////////////////////////////////
// Event posting system

class CSentEvent {
public:
  CEntityPointer se_penEntity;
  CEntityEvent *se_peeEvent;
  inline void Clear(void) { se_penEntity = NULL; }
};

static CStaticStackArray<CSentEvent> _aseSentEvents;  // delayed events
/* Send an event to this entity. */
void CEntity::SendEvent(const CEntityEvent &ee)
{
  ASSERT(this!=NULL);
  CSentEvent &se = _aseSentEvents.Push();
  se.se_penEntity = this;
  se.se_peeEvent = ((CEntityEvent&)ee).MakeCopy();  // discard const qualifier
}

// find entities in a box (box must be around this entity)
void CEntity::FindEntitiesInRange(
  const FLOATaabbox3D &boxRange, CDynamicContainer<CEntity> &cen, BOOL bCollidingOnly)
{
  ASSERT(GetFPUPrecision()==FPT_24BIT);

  // for each entity in the world of this entity
  FOREACHINDYNAMICCONTAINER(en_pwoWorld->wo_cenEntities, CEntity, iten) {
    // if it is zoning brush entity
    if (iten->en_RenderType == CEntity::RT_BRUSH && (iten->en_ulFlags&ENF_ZONING)) {
      // get first mip in its brush
      CBrushMip *pbm = iten->en_pbrBrush->GetFirstMip();
      // if the mip doesn't touch the box
      if (!pbm->bm_boxBoundingBox.HasContactWith(boxRange)) {
        // skip it
        continue;
      }

      // for all sectors in this mip
      FOREACHINDYNAMICARRAY(pbm->bm_abscSectors, CBrushSector, itbsc) {
        // if the sector doesn't touch the box
        if (!itbsc->bsc_boxBoundingBox.HasContactWith(boxRange)) {
          // skip it
          continue;
        }

        // for all entities in the sector
        {FOREACHDSTOFSRC(itbsc->bsc_rsEntities, CEntity, en_rdSectors, pen)
          // if the model entity touches the box
          if ((pen->en_RenderType==RT_MODEL || pen->en_RenderType==RT_EDITORMODEL)
            && boxRange.HasContactWith(
            FLOATaabbox3D(pen->GetPlacement().pl_PositionVector, pen->en_fSpatialClassificationRadius))) {

            // if it has collision box
            if (pen->en_pciCollisionInfo!=NULL) {
              // for each sphere
              FOREACHINSTATICARRAY(pen->en_pciCollisionInfo->ci_absSpheres, CMovingSphere, itms) {
                // project it
                itms->ms_vRelativeCenter0 = itms->ms_vCenter*pen->en_mRotation+pen->en_plPlacement.pl_PositionVector;
                // if the sphere touches the range
                if (boxRange.HasContactWith(FLOATaabbox3D(itms->ms_vRelativeCenter0, itms->ms_fR))) {
                  // add it to container
                  if (!cen.IsMember(pen)) {
                    cen.Add(pen);
                  }
                  goto next_entity;
                }
              }
            // if no collision box, but non-colliding are allowed
            } else if (!bCollidingOnly) {
              // add it to container
              if (!cen.IsMember(pen)) {
                cen.Add(pen);
              }
            }
          // if the brush entity touches the box
          } else if (pen->en_RenderType==RT_BRUSH && 
            boxRange.HasContactWith(
            FLOATaabbox3D(pen->GetPlacement().pl_PositionVector, pen->en_fSpatialClassificationRadius))) {
            // if the brush touches the box
            if (boxRange.HasContactWith(pen->en_pbrBrush->GetFirstMip()->bm_boxBoundingBox)) {
              // add it to container
              if (!cen.IsMember(pen)) {
                cen.Add(pen);
              }
            }
          } else if ((pen->en_RenderType==RT_SKAMODEL  || pen->en_RenderType==RT_SKAEDITORMODEL)
            && boxRange.HasContactWith(
            FLOATaabbox3D(pen->GetPlacement().pl_PositionVector, pen->en_fSpatialClassificationRadius))) {
            // if it has collision box
            if (pen->en_pciCollisionInfo!=NULL) {
              // for each sphere
              FOREACHINSTATICARRAY(pen->en_pciCollisionInfo->ci_absSpheres, CMovingSphere, itms) {
                // project it
                itms->ms_vRelativeCenter0 = itms->ms_vCenter*pen->en_mRotation+pen->en_plPlacement.pl_PositionVector;
                // if the sphere touches the range
                if (boxRange.HasContactWith(FLOATaabbox3D(itms->ms_vRelativeCenter0, itms->ms_fR))) {
                  // add it to container
                  if (!cen.IsMember(pen)) {
                    cen.Add(pen);
                  }
                  goto next_entity;
                }
              }
            // if no collision box, but non-colliding are allowed
            } else if (!bCollidingOnly) {
              // add it to container
              if (!cen.IsMember(pen)) {
                cen.Add(pen);
              }
            }
          }
          next_entity:;
        ENDFOR}
      }
    }
  }
}

/* Send an event to all entities in a box (box must be around this entity). */
void CEntity::SendEventInRange(const CEntityEvent &ee, const FLOATaabbox3D &boxRange)
{
  // find entities in the range
  CDynamicContainer<CEntity> cenToReceive;
  FindEntitiesInRange(boxRange, cenToReceive, FALSE);

  // for each entity in container
  FOREACHINDYNAMICCONTAINER(cenToReceive, CEntity, iten) {
    // send the event to it
    iten->SendEvent(ee);
  }
}

/* Handle all sent events. */
void CEntity::HandleSentEvents(void)
{
  CSetFPUPrecision FPUPrecision(FPT_24BIT);

  // while there are any unhandled events
  INDEX iFirstEvent = 0;
  while (iFirstEvent<_aseSentEvents.Count()) {
    CSentEvent &se = _aseSentEvents[iFirstEvent];
    // if not allowed to execute now
    if (!se.se_penEntity->IsAllowedForPrediction()) {
      // skip it
      iFirstEvent++;
      continue;
    }
    // if the entity is not destroyed
    if (!(se.se_penEntity->en_ulFlags&ENF_DELETED)) {
      // handle the current event
      se.se_penEntity->HandleEvent(*se.se_peeEvent);
    }
    // go to next event
    iFirstEvent++;
  }

  // for each event
  for(INDEX iee=0; iee<_aseSentEvents.Count(); iee++) {
    CSentEvent &se = _aseSentEvents[iee];
    // release the entity and destroy the event
    se.se_penEntity = NULL;
    delete se.se_peeEvent;
    se.se_peeEvent = NULL;
  }

  // flush all events
  _aseSentEvents.PopAll();
}

/////////////////////////////////////////////////////////////////////
// DLL class interface

/* Initialize for being virtual entity that is not rendered. */
void CEntity::InitAsVoid(void)
{
  en_RenderType = RT_VOID;
  en_pbrBrush = NULL;
}
/*
 * Initialize for beeing a model object.
 */
void CEntity::InitAsModel(void)
{
  // set render type to model
  en_RenderType = RT_MODEL;
  // create a model object
  en_pmoModelObject = new CModelObject;
  en_psiShadingInfo = new CShadingInfo;
  en_ulFlags &= ~ENF_VALIDSHADINGINFO;
}
/*
 * Initialize for beeing a ska model object.
 */
void CEntity::InitAsSkaModel(void)
{
  en_RenderType = RT_SKAMODEL;
  en_psiShadingInfo = new CShadingInfo;
  en_ulFlags &= ~ENF_VALIDSHADINGINFO;
}

/*
 * Initialize for beeing a terrain object.
 */
void CEntity::InitAsTerrain(void)
{
  en_RenderType = RT_TERRAIN;
  // if there is no existing terrain
  if(en_ptrTerrain == NULL) {
    // create a new empty terrain in the brush archive of current world
    en_ptrTerrain = en_pwoWorld->wo_taTerrains.ta_atrTerrains.New();
    en_ptrTerrain->tr_penEntity = this;

    // Create empty terrain
    en_ptrTerrain->CreateEmptyTerrain_t(257,257);
    en_ptrTerrain->SetTerrainSize(FLOAT3D(256,50,256));
    en_ptrTerrain->SetShadowMapsSize(0,0);
    en_ptrTerrain->UpdateShadowMap();
  }
  UpdateSpatialRange();
}

/*
 * Initialize for beeing a model object, for editor only.
 */
void CEntity::InitAsEditorModel(void)
{
  // set render type to model
  en_RenderType = RT_EDITORMODEL;
  // create a model object
  en_pmoModelObject = new CModelObject;
  en_psiShadingInfo = new CShadingInfo;
  en_ulFlags &= ~ENF_VALIDSHADINGINFO;
}
/*
 * Initialize for beeing a ska model object, for editor only.
 */
void CEntity::InitAsSkaEditorModel(void)
{
  // set render type to model
  en_RenderType = RT_SKAEDITORMODEL;
  // create a model object
  en_psiShadingInfo = new CShadingInfo;
  en_ulFlags &= ~ENF_VALIDSHADINGINFO;
}
/*
 * Initialize for beeing a brush object.
 */
void CEntity::InitAsBrush(void)
{
  ASSERT(GetFPUPrecision()==FPT_24BIT);

  // set render type to brush
  en_RenderType = RT_BRUSH;
  // if there is no existing brush
  if (en_pbrBrush == NULL) {
    // create a new empty brush in the brush archive of current world
    en_pbrBrush = en_pwoWorld->wo_baBrushes.ba_abrBrushes.New();
    en_pbrBrush->br_penEntity = this;
    // create a brush mip for it
    CBrushMip *pbmMip = new CBrushMip;
    // add it to list
    en_pbrBrush->br_lhBrushMips.AddTail(pbmMip->bm_lnInBrush);
    // set back-pointer to the brush
    pbmMip->bm_pbrBrush = en_pbrBrush;
    en_pbrBrush->CalculateBoundingBoxes();
  }
  UpdateSpatialRange();
}

/*
 * Initialize for beeing a field brush object.
 */
void CEntity::InitAsFieldBrush(void)
{
  ASSERT(GetFPUPrecision()==FPT_24BIT);

  // set render type to brush
  en_RenderType = RT_FIELDBRUSH;
  // if there is no existing brush
  if (en_pbrBrush == NULL) {
    // create a new empty brush in the brush archive of current world
    en_pbrBrush = en_pwoWorld->wo_baBrushes.ba_abrBrushes.New();
    en_pbrBrush->br_penEntity = this;
    // create a brush mip for it
    CBrushMip *pbmMip = new CBrushMip;
    // add it to list
    en_pbrBrush->br_lhBrushMips.AddTail(pbmMip->bm_lnInBrush);
    // set back-pointer to the brush
    pbmMip->bm_pbrBrush = en_pbrBrush;
    en_pbrBrush->CalculateBoundingBoxes();
  }
  UpdateSpatialRange();
}

/*
 *  Switch to Model/Editor model
 */
void CEntity::SwitchToModel(void)
{
  // change to editor model
  if(en_RenderType==RT_MODEL || en_RenderType==RT_EDITORMODEL) {
    en_RenderType = RT_MODEL;
  } else if( en_RenderType==RT_SKAMODEL || en_RenderType==RT_SKAEDITORMODEL) {
    en_RenderType = RT_SKAMODEL;
  } else {
    // it must be model (not brush)
    ASSERT(FALSE);
  }
}
void CEntity::SwitchToEditorModel(void)
{
  // change to editor model
  if(en_RenderType==RT_MODEL || en_RenderType==RT_EDITORMODEL) {
    en_RenderType = RT_EDITORMODEL;
  } else if( en_RenderType==RT_SKAMODEL || en_RenderType==RT_SKAEDITORMODEL) {
    en_RenderType = RT_SKAEDITORMODEL;
  } else {
    // it must be model (not brush)
    ASSERT(FALSE);
  }
}

/////////////////////////////////////////////////////////////////////
// Model manipulation
/* Set the model data for model entity. */
void CEntity::SetModel(const CTFileName &fnmModel)
{
  ASSERT(en_RenderType==RT_MODEL || en_RenderType==RT_EDITORMODEL);
  // try to
  try {
    // load the new model data
    en_pmoModelObject->SetData_t(fnmModel);
  // if failed
  } catch(char *strError) {
    (void)strError;
    DECLARE_CTFILENAME(fnmDefault, "Models\\Editor\\Axis.mdl");
    // try to
    try {
      // load the default model data
      en_pmoModelObject->SetData_t(fnmDefault);
    // if failed
    } catch(char *strErrorDefault) {
      FatalError(TRANS("Cannot load default model '%s':\n%s"),
        (const char *) (CTString&)fnmDefault, strErrorDefault);
    }
  }
  UpdateSpatialRange();
  FindCollisionInfo();
}

void CEntity::SetModel(SLONG idModelComponent)
{
  ASSERT(en_RenderType==RT_MODEL || en_RenderType==RT_EDITORMODEL);
  CEntityComponent *pecModel = en_pecClass->ComponentForTypeAndID(
    ECT_MODEL, idModelComponent);
  en_pmoModelObject->SetData(pecModel->ec_pmdModel);
  UpdateSpatialRange();
  FindCollisionInfo();
}

void CEntity::SetSkaColisionInfo()
{
  ASSERT(en_RenderType==RT_SKAMODEL || en_RenderType==RT_SKAEDITORMODEL);
  // if there is no colision boxes for ska model 
  if(en_pmiModelInstance->mi_cbAABox.Count() == 0) {
    // add one default colision box
    en_pmiModelInstance->AddColisionBox("Default",FLOAT3D(-0.5,0,-0.5),FLOAT3D(0.5,2,0.5));
  }
  UpdateSpatialRange();
  FindCollisionInfo();
}

void CEntity::SetSkaModel_t(const CTString &fnmModel)
{
  ASSERT(en_RenderType==RT_SKAMODEL || en_RenderType==RT_SKAEDITORMODEL);
  // if model instance allready exists
  if(en_pmiModelInstance!=NULL) {
    // release it first
    en_pmiModelInstance->Clear();
  }
  try {
    // load the new model data
    en_pmiModelInstance = ParseSmcFile_t(fnmModel);
  } catch (char *strErrorDefault) {
    throw(strErrorDefault);
  }
  SetSkaColisionInfo();
}
BOOL CEntity::SetSkaModel(const CTString &fnmModel)
{
  ASSERT(en_RenderType==RT_SKAMODEL || en_RenderType==RT_SKAEDITORMODEL);
  // try to
  try {
    SetSkaModel_t(fnmModel);
  // if failed
  } catch(char *strError) {
    (void)strError;
    WarningMessage("%s\n\rLoading default model.\n", strError);
    DECLARE_CTFILENAME(fnmDefault, "Models\\Editor\\Ska\\Axis.smc");    
    // try to
    try {
      // load the default model data
      en_pmiModelInstance = ParseSmcFile_t(fnmDefault);
    // if failed
    } catch(char *strErrorDefault) {
      FatalError(TRANS("Cannot load default model '%s':\n%s"),
        (const char *) (CTString&)fnmDefault, strErrorDefault);
    }
    // set colision info for default model
    SetSkaColisionInfo();
    return FALSE;
  }
  return TRUE;
}
// set/get model main blend color

void CEntity::SetModelColor( const COLOR colBlend)
{
  ASSERT(en_RenderType==RT_MODEL || en_RenderType==RT_EDITORMODEL);
  en_pmoModelObject->mo_colBlendColor = colBlend;
}

COLOR CEntity::GetModelColor(void) const
{
  ASSERT(en_RenderType==RT_MODEL || en_RenderType==RT_EDITORMODEL);
  return en_pmoModelObject->mo_colBlendColor;
}


/* Get the model data for model entity. */

const CTFileName &CEntity::GetModel(void)
{
  ASSERT(en_RenderType==RT_MODEL || en_RenderType==RT_EDITORMODEL);
  return ((CAnimData*)en_pmoModelObject->GetData())->GetName();
}
/* Start new animation for model entity. */
void CEntity::StartModelAnim(INDEX iNewModelAnim, ULONG ulFlags)
{
  ASSERT(en_RenderType==RT_MODEL || en_RenderType==RT_EDITORMODEL);
  en_pmoModelObject->PlayAnim(iNewModelAnim, ulFlags);
}

/* Set the main texture data for model entity. */
void CEntity::SetModelMainTexture(const CTFileName &fnmTexture)
{
  ASSERT(en_RenderType==RT_MODEL || en_RenderType==RT_EDITORMODEL);
  // try to
  try {
    // load the texture data
    en_pmoModelObject->mo_toTexture.SetData_t(fnmTexture);

  // if failed
  } catch(char *strError) {
    (void)strError;
    DECLARE_CTFILENAME(fnmDefault, "Textures\\Editor\\Default.tex");
    // try to
    try {
      // load the default model data
      en_pmoModelObject->mo_toTexture.SetData_t(fnmDefault);
    // if failed
    } catch(char *strErrorDefault) {
      FatalError(TRANS("Cannot load default texture '%s':\n%s"),
        (const char *) (CTString&)fnmDefault, strErrorDefault);
    }
  }
}
void CEntity::SetModelMainTexture(SLONG idTextureComponent)
{
  ASSERT(en_RenderType==RT_MODEL || en_RenderType==RT_EDITORMODEL);
  CEntityComponent *pecTexture = en_pecClass->ComponentForTypeAndID(
    ECT_TEXTURE, idTextureComponent);
  en_pmoModelObject->mo_toTexture.SetData(pecTexture->ec_ptdTexture);
}
/* Get the main texture data for model entity. */
const CTFileName &CEntity::GetModelMainTexture(void)
{
  ASSERT(en_RenderType==RT_MODEL || en_RenderType==RT_EDITORMODEL);
  return en_pmoModelObject->mo_toTexture.GetData()->GetName();
}
/* Start new animation for main texture of model entity. */
void CEntity::StartModelMainTextureAnim(INDEX iNewTextureAnim)
{
  ASSERT(en_RenderType==RT_MODEL || en_RenderType==RT_EDITORMODEL);
  en_pmoModelObject->mo_toTexture.StartAnim(iNewTextureAnim);
}

/* Set the reflection texture data for model entity. */
void CEntity::SetModelReflectionTexture(SLONG idTextureComponent)
{
  ASSERT(en_RenderType==RT_MODEL || en_RenderType==RT_EDITORMODEL);
  CEntityComponent *pecTexture = en_pecClass->ComponentForTypeAndID(
    ECT_TEXTURE, idTextureComponent);
  en_pmoModelObject->mo_toReflection.SetData(pecTexture->ec_ptdTexture);
}
/* Set the specular texture data for model entity. */
void CEntity::SetModelSpecularTexture(SLONG idTextureComponent)
{
  ASSERT(en_RenderType==RT_MODEL || en_RenderType==RT_EDITORMODEL);
  CEntityComponent *pecTexture = en_pecClass->ComponentForTypeAndID(
    ECT_TEXTURE, idTextureComponent);
  en_pmoModelObject->mo_toSpecular.SetData(pecTexture->ec_ptdTexture);
}

/* Add attachment to model */
void CEntity::AddAttachment(INDEX iAttachment, ULONG ulIDModel, ULONG ulIDTexture)
{
  // add attachment
  CModelObject &mo = en_pmoModelObject->AddAttachmentModel(iAttachment)->amo_moModelObject;
  // update model data
  CEntityComponent *pecWeaponModel = ComponentForTypeAndID( ECT_MODEL, ulIDModel);
  mo.SetData(pecWeaponModel->ec_pmdModel);
  // update texture data if different
  CEntityComponent *pecWeaponTexture = ComponentForTypeAndID( ECT_TEXTURE, ulIDTexture);
  mo.SetTextureData(pecWeaponTexture->ec_ptdTexture);
}
void CEntity::AddAttachment(INDEX iAttachment, CTFileName fnModel, CTFileName fnTexture)
{
  if( fnModel == CTString("")) return;
  CModelObject *pmo = GetModelObject();
  ASSERT( pmo != NULL);
  if( pmo == NULL) return;

  CAttachmentModelObject *pamo = pmo->AddAttachmentModel( iAttachment);
  try
  {
    pamo->amo_moModelObject.SetData_t( fnModel);
  }
  catch(char *strError)
  {
    (void) strError;
    pmo->RemoveAttachmentModel( iAttachment);
    return;
  }

  try
  {
    pamo->amo_moModelObject.mo_toTexture.SetData_t( fnTexture);
  }
  catch(char *strError)
  {
    (void) strError;
  }
}
/* Set the reflection texture data for attachment model entity. */
void CEntity::SetModelAttachmentReflectionTexture(INDEX iAttachment, SLONG idTextureComponent)
{
  ASSERT(en_RenderType==RT_MODEL || en_RenderType==RT_EDITORMODEL);
  CModelObject &mo = en_pmoModelObject->GetAttachmentModel(iAttachment)->amo_moModelObject;
  CEntityComponent *pecTexture = en_pecClass->ComponentForTypeAndID(
    ECT_TEXTURE, idTextureComponent);
  mo.mo_toReflection.SetData(pecTexture->ec_ptdTexture);
}
/* Set the specular texture data for attachment model entity. */
void CEntity::SetModelAttachmentSpecularTexture(INDEX iAttachment, SLONG idTextureComponent)
{
  ASSERT(en_RenderType==RT_MODEL || en_RenderType==RT_EDITORMODEL);
  CModelObject &mo = en_pmoModelObject->GetAttachmentModel(iAttachment)->amo_moModelObject;
  CEntityComponent *pecTexture = en_pecClass->ComponentForTypeAndID(
    ECT_TEXTURE, idTextureComponent);
  mo.mo_toSpecular.SetData(pecTexture->ec_ptdTexture);
}

// Get all vertices of model entity in absolute space
void CEntity::GetModelVerticesAbsolute( CStaticStackArray<FLOAT3D> &avVertices, FLOAT fNormalOffset, FLOAT fMipFactor)
{
  ASSERT(en_RenderType==RT_MODEL || en_RenderType==RT_EDITORMODEL ||
         en_RenderType==RT_SKAMODEL || en_RenderType==RT_SKAEDITORMODEL);
  // get placement
  CPlacement3D plPlacement = GetLerpedPlacement();
  // calculate rotation matrix
  FLOATmatrix3D mRotation;
  MakeRotationMatrixFast(mRotation, plPlacement.pl_OrientationAngle);
  if(en_RenderType==RT_MODEL || en_RenderType==RT_EDITORMODEL) {
    en_pmoModelObject->GetModelVertices( avVertices, mRotation, plPlacement.pl_PositionVector, fNormalOffset, fMipFactor);
  } else {
    GetModelInstance()->GetModelVertices( avVertices, mRotation, plPlacement.pl_PositionVector, fNormalOffset, fMipFactor);
  }
}

void EntityAdjustBonesCallback(void *pData)
{
  ((CEntity*)pData)->AdjustBones();
}
void EntityAdjustShaderParamsCallback(void *pData,INDEX iSurfaceID,CShader *pShader,ShaderParams &spParams)
{
  ((CEntity*)pData)->AdjustShaderParams(iSurfaceID,pShader,spParams);
}

// Returns true if bone exists and sets two given vectors as start and end point of specified bone
BOOL CEntity::GetBoneRelPosition(INDEX iBoneID, FLOAT3D &vStartPoint, FLOAT3D &vEndPoint)
{
  ASSERT(en_RenderType==RT_SKAMODEL || en_RenderType==RT_SKAEDITORMODEL);
  RM_SetObjectPlacement(CPlacement3D(FLOAT3D(0.0f,0.0f,0.0f),ANGLE3D(0.0f,0.0f,0.0f)));
  RM_SetBoneAdjustCallback(&EntityAdjustBonesCallback,this);
  return RM_GetBoneAbsPosition(*GetModelInstance(),iBoneID,vStartPoint,vEndPoint);
}

// Returns true if bone exists and sets two given vectors as start and end point of specified bone
BOOL CEntity::GetBoneAbsPosition(INDEX iBoneID, FLOAT3D &vStartPoint, FLOAT3D &vEndPoint)
{
  ASSERT(en_RenderType==RT_SKAMODEL || en_RenderType==RT_SKAEDITORMODEL);
  RM_SetObjectPlacement(GetLerpedPlacement());
  RM_SetBoneAdjustCallback(&EntityAdjustBonesCallback,this);
  return RM_GetBoneAbsPosition(*GetModelInstance(),iBoneID,vStartPoint,vEndPoint);
}

// Callback function for aditional bone adjustment
void CEntity::AdjustBones()
{
}

// Callback function for aditional shader params adjustment
void CEntity::AdjustShaderParams(INDEX iSurfaceID,CShader *pShader,ShaderParams &spParams)
{
}

// precache given component
void CEntity::PrecacheModel(SLONG slID)
{
  en_pecClass->ec_pdecDLLClass->PrecacheModel(slID);
}
void CEntity::PrecacheTexture(SLONG slID)
{
  en_pecClass->ec_pdecDLLClass->PrecacheTexture(slID);
}
void CEntity::PrecacheSound(SLONG slID)
{
  en_pecClass->ec_pdecDLLClass->PrecacheSound(slID);
}
void CEntity::PrecacheClass(SLONG slID, INDEX iUser /* = -1*/)
{
  en_pecClass->ec_pdecDLLClass->PrecacheClass(slID, iUser);
}

CAutoPrecacheSound::CAutoPrecacheSound()
{
  apc_psd = NULL;
}
CAutoPrecacheSound::~CAutoPrecacheSound()
{
  if (apc_psd!=NULL) {
    _pSoundStock->Release(apc_psd);
  }
}

void CAutoPrecacheSound::Precache(const CTFileName &fnm)
{
  if (apc_psd!=NULL) {
    _pSoundStock->Release(apc_psd);
  }
  try {
    if (fnm!="") {
      apc_psd = _pSoundStock->Obtain_t(fnm);
    }
  } catch (char *strError) {
    CPrintF("%s\n", strError);
  }
}

CAutoPrecacheModel::CAutoPrecacheModel()
{
  apc_pmd = NULL;
}
CAutoPrecacheModel::~CAutoPrecacheModel()
{
  if (apc_pmd!=NULL) {
    _pModelStock->Release(apc_pmd);
  }
}

void CAutoPrecacheModel::Precache(const CTFileName &fnm)
{
  if (apc_pmd!=NULL) {
    _pModelStock->Release(apc_pmd);
  }
  try {
    if (fnm!="") {
      apc_pmd = _pModelStock->Obtain_t(fnm);
    }
  } catch (char *strError) {
    CPrintF("%s\n", strError);
  }
}

CAutoPrecacheTexture::CAutoPrecacheTexture()
{
  apc_ptd = NULL;
}
CAutoPrecacheTexture::~CAutoPrecacheTexture()
{
  if (apc_ptd!=NULL) {
    _pTextureStock->Release(apc_ptd);
  }
}

void CAutoPrecacheTexture::Precache(const CTFileName &fnm)
{
  if (apc_ptd!=NULL) {
    _pTextureStock->Release(apc_ptd);
  }
  try {
    if (fnm!="") {
      apc_ptd = _pTextureStock->Obtain_t(fnm);
    }
  } catch (char *strError) {
    CPrintF("%s\n", strError);
  }
}

/* Get a filename for a component of given id. */
const CTFileName &CEntity::FileNameForComponent(SLONG slType, SLONG slID)
{
  // find the component
  CEntityComponent *pec = en_pecClass->ComponentForTypeAndID(
    (EntityComponentType)slType, slID);
  // the component must exist
  ASSERT(pec!=NULL);
  // get its name
  return pec->ec_fnmComponent;
}

// Get data for a texture component
CTextureData *CEntity::GetTextureDataForComponent(SLONG slID)
{
  CEntityComponent *pec = ComponentForTypeAndID( ECT_TEXTURE, slID);
  if (pec!=NULL) {
    return pec->ec_ptdTexture;
  } else {
    return NULL;
  }
}

// Get data for a model component
CModelData *CEntity::GetModelDataForComponent(SLONG slID)
{
  CEntityComponent *pec = ComponentForTypeAndID( ECT_MODEL, slID);
  if (pec!=NULL) {
    return pec->ec_pmdModel;
  } else {
    return NULL;
  }
}

/* Remove attachment from model */
void CEntity::RemoveAttachment(INDEX iAttachment)
{
  // remove attachment
  en_pmoModelObject->RemoveAttachmentModel(iAttachment);
}

/* Initialize last positions structure for particles. */
CLastPositions *CEntity::GetLastPositions(INDEX ctPositions)
{
  TIME tmNow = _pTimer->CurrentTick();
  if (en_plpLastPositions==NULL) {
    en_plpLastPositions = new CLastPositions;
    en_plpLastPositions->lp_avPositions.New(ctPositions);
    en_plpLastPositions->lp_ctUsed = 0;
    en_plpLastPositions->lp_iLast = 0;
    en_plpLastPositions->lp_tmLastAdded = tmNow;
    const FLOAT3D &vNow = GetPlacement().pl_PositionVector;
    for(INDEX iPos = 0; iPos<ctPositions; iPos++) {
      en_plpLastPositions->lp_avPositions[iPos] = vNow;
    }
  }

  while(en_plpLastPositions->lp_tmLastAdded<tmNow) {
    en_plpLastPositions->AddPosition(en_plpLastPositions->GetPosition(0));
  }

  return en_plpLastPositions;
}


/* Get absolute position of point on entity given relative to its size. */
void CEntity::GetEntityPointRatio(const FLOAT3D &vRatio, FLOAT3D &vAbsPoint, BOOL bLerped)
{
  ASSERT(bLerped || GetFPUPrecision()==FPT_24BIT);

  if (en_RenderType!=RT_MODEL && en_RenderType!=RT_EDITORMODEL &&
      en_RenderType!=RT_SKAMODEL && en_RenderType!=RT_SKAEDITORMODEL && 
      en_RenderType!=RT_BRUSH)  {
    ASSERT(FALSE);
    vAbsPoint = en_plPlacement.pl_PositionVector;
    return;
  }

  FLOAT3D vMin, vMax;

  if (en_RenderType==RT_BRUSH)
  {
    CBrushMip *pbmMip = en_pbrBrush->GetFirstMip();
    vMin = pbmMip->bm_boxBoundingBox.Min();
    vMax = pbmMip->bm_boxBoundingBox.Max();
    FLOAT3D vOff = vMax-vMin;
    vOff(1) *= vRatio(1);
    vOff(2) *= vRatio(2);
    vOff(3) *= vRatio(3);
    vAbsPoint = vMin+vOff;
  }
  else
  {
    if (_pNetwork->ga_ulDemoMinorVersion<=2) {
      vMin = en_pmoModelObject->GetCollisionBoxMin(GetCollisionBoxIndex());
      vMax = en_pmoModelObject->GetCollisionBoxMax(GetCollisionBoxIndex());
    } else {
      INDEX iEq;
      FLOATaabbox3D box;
      GetCollisionBoxParameters(GetCollisionBoxIndex(), box, iEq);
      vMin = box.Min();
      vMax = box.Max();
    }
    FLOAT3D vOff = vMax-vMin;
    vOff(1) *= vRatio(1);
    vOff(2) *= vRatio(2);
    vOff(3) *= vRatio(3);
    FLOAT3D vPos = vMin+vOff;
    if( bLerped)
    {
      CPlacement3D plLerped=GetLerpedPlacement();
      FLOATmatrix3D mRot;
      MakeRotationMatrixFast(mRot, plLerped.pl_OrientationAngle);
      vAbsPoint=plLerped.pl_PositionVector+vPos*mRot;
    }
    else
    {
      vAbsPoint = en_plPlacement.pl_PositionVector+vPos*en_mRotation;
    }
  }
}

/* Get absolute position of point on entity given in meters. */
void CEntity::GetEntityPointFixed(const FLOAT3D &vFixed, FLOAT3D &vAbsPoint)
{
  vAbsPoint = en_plPlacement.pl_PositionVector+vFixed*en_mRotation;
}
/* Get sector that given point is in - point must be inside this entity. */
CBrushSector *CEntity::GetSectorFromPoint(const FLOAT3D &vPointAbs)
{
  // for each sector around entity
  {FOREACHSRCOFDST(en_rdSectors, CBrushSector, bsc_rsEntities, pbsc)
    // if point is in this sector
    if( pbsc->bsc_bspBSPTree.TestSphere(vPointAbs, 0.01)>=0) {
      // return that
      return pbsc;
    }
  ENDFOR;}
  return NULL;
}

/* Model change notify */
void CEntity::ModelChangeNotify(void)
{
  // if this is ska model
  if(en_RenderType == RT_SKAMODEL || en_RenderType == RT_SKAEDITORMODEL) {
    if(GetModelInstance()==NULL) {
      return;
    }

  // this is old model
  } else {
    if (en_pmoModelObject==NULL || en_pmoModelObject->GetData()==NULL) {
      return;
    }
  }

  UpdateSpatialRange();
  FindCollisionInfo();
}

void CEntity::TerrainChangeNotify(void)
{
//  GetTerrain()->RemoveLayer(0,FALSE);
//  GetTerrain()->AddDefaultLayer_t();
  GetTerrain()->ReBuildTerrain(TRUE);
  UpdateSpatialRange();
}

// map world polygon to/from indices
CBrushPolygon *CEntity::GetWorldPolygonPointer(INDEX ibpo)
{
  if (ibpo==-1) {
    return NULL;
  } else {
    return en_pwoWorld->wo_baBrushes.ba_apbpo[ibpo];
  }
}
INDEX CEntity::GetWorldPolygonIndex(CBrushPolygon *pbpo)
{
  if (pbpo==NULL) {
    return -1;
  } else {
    return pbpo->bpo_iInWorld;
  }
}

/////////////////////////////////////////////////////////////////////
// Sound functions
void CEntity::PlaySound(CSoundObject &so, SLONG idSoundComponent, SLONG slPlayType)
{
  CEntityComponent *pecSound = en_pecClass->ComponentForTypeAndID(ECT_SOUND, idSoundComponent);
  //so.Stop();
  so.Play(pecSound->ec_psdSound, slPlayType);
}

double CEntity::GetSoundLength(SLONG idSoundComponent)
{
  CEntityComponent *pecSound = en_pecClass->ComponentForTypeAndID(ECT_SOUND, idSoundComponent);
  return pecSound->ec_psdSound->GetSecondsLength();
}

void CEntity::PlaySound(CSoundObject &so, const CTFileName &fnmSound, SLONG slPlayType)
{
  // try to
  try {
    // load the sound data
    //so.Stop();
    so.Play_t(fnmSound, slPlayType);
  // if failed
  } catch(char *strError) {
    (void)strError;
    DECLARE_CTFILENAME(fnmDefault, "Sounds\\Default.wav");
    // try to
    try {
      // load the default sound data
      so.Play_t(fnmDefault, slPlayType);
    // if failed
    } catch(char *strErrorDefault) {
      FatalError(TRANS("Cannot load default sound '%s':\n%s"),
        (const char *) (CTString&)fnmDefault, strErrorDefault);
    }
  }
}

/////////////////////////////////////////////////////////////////////
/*
 * Apply some damage directly to one entity.
 */
void CEntity::InflictDirectDamage(CEntity *penToDamage, CEntity *penInflictor, enum DamageType dmtType,
  FLOAT fDamageAmmount, const FLOAT3D &vHitPoint, const FLOAT3D &vDirection)
{
  ASSERT(GetFPUPrecision()==FPT_24BIT);

  // if any of the entities are not allowed to execute now
  if (!IsAllowedForPrediction()
    ||!penToDamage->IsAllowedForPrediction()
    ||!penInflictor->IsAllowedForPrediction()) {
    // do nothing
    return;
  }

  // if significant damage
  if (fDamageAmmount>0) {
    penToDamage->ReceiveDamage(penInflictor, dmtType, fDamageAmmount, vHitPoint, vDirection);
  }
}

// find intensity of current entity at given distance
static inline FLOAT IntensityAtDistance(
  FLOAT fDamageAmmount, FLOAT fHotSpotRange, FLOAT fFallOffRange, FLOAT fDistance)
{
  // if further than fall-off range
  if (fDistance>fFallOffRange) {
    // intensity is zero
    return 0;
  // if closer than hot-spot range
  } else if (fDistance<fHotSpotRange) {
    // intensity is maximum
    return fDamageAmmount;
  // if between fall-off and hot-spot range
  } else {
    // interpolate
    return fDamageAmmount*(fFallOffRange-fDistance)/(fFallOffRange-fHotSpotRange);
  }
}

// check if a range damage can hit given model entity
static BOOL CheckModelRangeDamage(
  CEntity &en, const FLOAT3D &vCenter, FLOAT &fMinD, FLOAT3D &vHitPos)
{
  CCollisionInfo *pci = en.en_pciCollisionInfo;
  if (pci==NULL) {
    return FALSE;
  }

  // create 3 points along entity
  const FLOATmatrix3D &mR = en.en_mRotation;
  const FLOAT3D &vO = en.en_plPlacement.pl_PositionVector;
  FLOAT3D avPoints[3];
  INDEX ctSpheres = pci->ci_absSpheres.Count();
  if (ctSpheres<1) {
    return FALSE;
  }
  avPoints[1] = pci->ci_absSpheres[ctSpheres-1].ms_vCenter*mR+vO;
  avPoints[2] = pci->ci_absSpheres[0].ms_vCenter*mR+vO;
  avPoints[0] = (avPoints[1]+avPoints[2])*0.5f;

  // check if any point can be hit
  BOOL bCanHit = FALSE;
  for(INDEX i=0; i<3; i++) {
    CCastRay crRay( &en, avPoints[i], vCenter);
    crRay.cr_ttHitModels = CCastRay::TT_NONE;     // only brushes block the damage
    crRay.cr_bHitTranslucentPortals = FALSE;
    crRay.cr_bPhysical = TRUE;
    en.en_pwoWorld->CastRay(crRay);
    if (crRay.cr_penHit==NULL) {
      bCanHit = TRUE;
      break;
    }
  }

  // if none can be hit
  if (!bCanHit) {
    // skip this entity
    return FALSE;
  }

  // find minimum distance
  fMinD = UpperLimit(0.0f);
  vHitPos = vO;
  // for each sphere
  FOREACHINSTATICARRAY(pci->ci_absSpheres, CMovingSphere, itms) {
    // project it
    itms->ms_vRelativeCenter0 = itms->ms_vCenter*en.en_mRotation+vO;
    FLOAT fD = (itms->ms_vRelativeCenter0-vCenter).Length()-itms->ms_fR;
    if (fD<fMinD) {
      fMinD = Min(fD, fMinD);
      vHitPos = itms->ms_vRelativeCenter0;
    }
  }
  if (fMinD<0) {
    fMinD = 0;
  }
  return TRUE;
}

// check if a range damage can hit given brush entity
static BOOL CheckBrushRangeDamage(
  CEntity &en, const FLOAT3D &vCenter, FLOAT &fMinD, FLOAT3D &vHitPos)
{
  // don't actually check for brushes, doesn't have to be too exact
  const FLOAT3D &vO = en.en_plPlacement.pl_PositionVector;
  fMinD = (vO-vCenter).Length();
  vHitPos = vO;
  return TRUE;
}

/* Apply some damage to all entities in some range. */
void CEntity::InflictRangeDamage(CEntity *penInflictor, enum DamageType dmtType,
  FLOAT fDamageAmmount, const FLOAT3D &vCenter, FLOAT fHotSpotRange, FLOAT fFallOffRange)
{
  ASSERT(GetFPUPrecision()==FPT_24BIT);

  // if any of the entities are not allowed to execute now
  if (!IsAllowedForPrediction()
    ||!penInflictor->IsAllowedForPrediction()) {
    // do nothing
    return;
  }

  // find entities in the range
  CDynamicContainer<CEntity> cenInRange;
  FindEntitiesInRange(FLOATaabbox3D(vCenter, fFallOffRange), cenInRange, TRUE);

  // for each entity in container
  FOREACHINDYNAMICCONTAINER(cenInRange, CEntity, iten) {
    CEntity &en = *iten;
    // if entity is not allowed to execute now
    if (!en.IsAllowedForPrediction()) {
      // do nothing
      continue;
    }

    // if can be hit
    FLOAT3D vHitPos;
    FLOAT fMinD;
    if (
      ((en.en_RenderType==RT_MODEL || en.en_RenderType==RT_EDITORMODEL ||
        en.en_RenderType==RT_SKAMODEL || en.en_RenderType==RT_SKAEDITORMODEL )&&
       CheckModelRangeDamage(en, vCenter, fMinD, vHitPos)) ||
      ((en.en_RenderType==RT_BRUSH)&&
        CheckBrushRangeDamage(en, vCenter, fMinD, vHitPos))) {

      // find damage ammount
      FLOAT fAmmount = IntensityAtDistance(fDamageAmmount, fHotSpotRange, fFallOffRange, fMinD);

      // if significant
      if (fAmmount>0) {
        // inflict damage to it
        en.ReceiveDamage(penInflictor, dmtType, fAmmount, vHitPos, (vHitPos-vCenter).Normalize());
      }
    }
  }
}

/* Apply some damage to all entities in a box (this doesn't test for obstacles). */
void CEntity::InflictBoxDamage(CEntity *penInflictor, enum DamageType dmtType,
  FLOAT fDamageAmmount, const FLOATaabbox3D &box)
{
  ASSERT(GetFPUPrecision()==FPT_24BIT);

  // if any of the entities are not allowed to execute now
  if (!IsAllowedForPrediction()
    ||!penInflictor->IsAllowedForPrediction()) {
    // do nothing
    return;
  }

  // find entities in the range
  CDynamicContainer<CEntity> cenInRange;
  FindEntitiesInRange(box, cenInRange, TRUE);

  // for each entity in container
  FOREACHINDYNAMICCONTAINER(cenInRange, CEntity, iten) {
    CEntity &en = *iten;
    //ASSERT(en.en_pciCollisionInfo!=NULL);  // assured by FindEntitiesInRange()
    if (en.en_pciCollisionInfo==NULL) {
      continue;
    }
    //CCollisionInfo *pci = en.en_pciCollisionInfo;
    // if entity is not allowed to execute now
    if (!en.IsAllowedForPrediction()) {
      // do nothing
      continue;
    }

    // if significant damage
    if (fDamageAmmount>0) {
      // inflict damage to it
      en.ReceiveDamage(penInflictor, dmtType,
        fDamageAmmount, box.Center(), 
        (box.Center()-en.GetPlacement().pl_PositionVector).Normalize());
    }
  }
}

// notify engine that gravity defined by this entity has changed
void CEntity::NotifyGravityChanged(void)
{
  if (_pNetwork->ga_ulDemoMinorVersion<=2) {
    // for each entity in the world of this entity
    FOREACHINDYNAMICCONTAINER(en_pwoWorld->wo_cenEntities, CEntity, iten) {
      CEntity *pen = &*iten;
      // if movable
      if (pen->en_ulPhysicsFlags&EPF_MOVABLE) {
        CMovableEntity *pmen = (CMovableEntity*)pen;
        // if the gravity has changed
        // add to movers
        pmen->AddToMovers();
      }
    }
  } else {
    // for each zoning brush in the world of this entity
    FOREACHINDYNAMICCONTAINER(en_pwoWorld->wo_cenEntities, CEntity, iten) {
      CEntity *penBrush = &*iten;
      if (iten->en_RenderType != CEntity::RT_BRUSH || !(iten->en_ulFlags&ENF_ZONING)) {
        continue;
      }
      CBrush3D *pbr = penBrush->en_pbrBrush;
      // get first brush mip
      CBrushMip *pbm = pbr->GetFirstMip();
      // for each sector in the brush mip
      {FOREACHINDYNAMICARRAY(pbm->bm_abscSectors, CBrushSector, itbsc) {
        // if controlled by this entity
        if ( penBrush->GetForceController(itbsc->GetForceType()) == this ) {
          // for each entity in the sector
          {FOREACHDSTOFSRC(itbsc->bsc_rsEntities, CEntity, en_rdSectors, pen) {
            // if movable
            if (pen->en_ulPhysicsFlags&EPF_MOVABLE) {
              CMovableEntity *pmen = (CMovableEntity*)pen;
              // add to movers
              pmen->AddToMovers();
            }
          ENDFOR}}
        }
      }}
    }
  }
}

// notify engine that collision of this entity was changed
void CEntity::NotifyCollisionChanged(void)
{
  if (en_pciCollisionInfo==NULL) {
    return;
  }

  // find colliding entities near this one
  static CStaticStackArray<CEntity*> apenNearEntities;
  en_pwoWorld->FindEntitiesNearBox(en_pciCollisionInfo->ci_boxCurrent, apenNearEntities);
  
  // for each of the found entities
  {for(INDEX ienFound=0; ienFound<apenNearEntities.Count(); ienFound++) {
    CEntity &enToNear = *apenNearEntities[ienFound];

    // if movable
    if (enToNear.en_ulPhysicsFlags&EPF_MOVABLE) {
      // add to movers
      ((CMovableEntity*)&enToNear)->AddToMovers();
    }
  }}
  apenNearEntities.PopAll();
}

// apply some damage to the entity (see event EDamage for more info)
void CEntity::ReceiveDamage(CEntity *penInflictor, enum DamageType dmtType,
  FLOAT fDamageAmmount, const FLOAT3D &vHitPoint, const FLOAT3D &vDirection)
{
  CEntityPointer penThis = this;  // keep this entity alive during this function
  // just throw an event that you are damaged (base entities don't really have health)
  EDamage eDamage;
  eDamage.penInflictor = penInflictor;
  eDamage.vDirection   = vDirection;
  eDamage.vHitPoint    = vHitPoint;
  eDamage.fAmount      = fDamageAmmount;
  eDamage.dmtType      = dmtType;
  SendEvent(eDamage);
}

/* Receive item through event */
BOOL CEntity::ReceiveItem(const CEntityEvent &ee)
{
  return FALSE;
}

/* Get entity info */
void *CEntity::GetEntityInfo(void)
{
  return NULL;
};
/* Fill in entity statistics - for AI purposes only */
BOOL CEntity::FillEntityStatistics(struct EntityStats *pes)
{
  return FALSE;
}

/////////////////////////////////////////////////////////////////////
// Overrides from CSerial

/*
 * Read from stream.
 */
void CEntity::Read_t( CTStream *istr) // throw char *
{
  ASSERT(GetFPUPrecision()==FPT_24BIT);

  // read base class data from stream
  if (istr->PeekID_t()==CChunkID("ENT4")) { // entity v4
    istr->ExpectID_t("ENT4");
    ULONG ulID;
    SLONG slSize;
    (*istr)>>ulID>>slSize;    // skip id and size
    (*istr)>>(ULONG &)en_RenderType
           >>en_ulPhysicsFlags
           >>en_ulCollisionFlags
           >>en_ulSpawnFlags
           >>en_ulFlags;
    (*istr)>>en_mRotation;
  } else if (istr->PeekID_t()==CChunkID("ENT3")) { // entity v3
    istr->ExpectID_t("ENT3");
    (*istr)>>(ULONG &)en_RenderType
           >>en_ulPhysicsFlags
           >>en_ulCollisionFlags
           >>en_ulSpawnFlags
           >>en_ulFlags;
    (*istr)>>en_mRotation;
  } else if (istr->PeekID_t()==CChunkID("ENT2")) { // entity v2
    istr->ExpectID_t("ENT2");
    (*istr)>>(ULONG &)en_RenderType
           >>en_ulPhysicsFlags
           >>en_ulCollisionFlags
           >>en_ulSpawnFlags
           >>en_ulFlags;
  } else {
    (*istr)>>(ULONG &)en_RenderType
           >>en_ulPhysicsFlags
           >>en_ulCollisionFlags
           >>en_ulFlags;
  }

  // clear flags for selection and caching info
  en_ulFlags &= ~(ENF_SELECTED|ENF_INRENDERING|ENF_VALIDSHADINGINFO);
  en_psiShadingInfo = NULL;
  en_pciCollisionInfo = NULL;

  // if this is a brush
  if ( en_RenderType == RT_BRUSH || en_RenderType == RT_FIELDBRUSH) {
    // read brush index in world's brush archive
    INDEX iBrush;
    (*istr)>>iBrush;
    en_pbrBrush = &en_pwoWorld->wo_baBrushes.ba_abrBrushes[iBrush];
    en_pbrBrush->br_penEntity = this;
  // if this is a terrain
  } else if (en_RenderType == RT_TERRAIN) {
    // read terrain index in world's terrain archive
    INDEX iTerrain;
    (*istr)>>iTerrain;
    en_ptrTerrain = &en_pwoWorld->wo_taTerrains.ta_atrTerrains[iTerrain];
    en_ptrTerrain->tr_penEntity = this;
    // force terrain regeneration (regenerate tiles on next render)
    en_ptrTerrain->ReBuildTerrain(TRUE);

  // if this is a model
  } else if ( en_RenderType == RT_MODEL || en_RenderType == RT_EDITORMODEL) {
    // create a new model object
    en_pmoModelObject = new CModelObject;
    en_psiShadingInfo = new CShadingInfo;
    en_ulFlags &= ~ENF_VALIDSHADINGINFO;

    // read model
    ReadModelObject_t(*istr, *en_pmoModelObject);
  // if this is a ska model
  } else if( en_RenderType == RT_SKAMODEL || en_RenderType == RT_SKAEDITORMODEL) {
    en_pmiModelInstance = CreateModelInstance("Temp");
    en_psiShadingInfo = new CShadingInfo;
    en_ulFlags &= ~ENF_VALIDSHADINGINFO;

    ReadModelInstance_t(*istr, *GetModelInstance());
  // if this is a void
  } else if (en_RenderType == RT_VOID) {
    en_pbrBrush = NULL;
  }

  // if the entity has a parent
  if (istr->PeekID_t()==CChunkID("PART")) { // parent
    // read the parent pointer and relative offset
    istr->ExpectID_t("PART");
    INDEX iParent;
    *istr>>iParent;
    extern BOOL _bReadEntitiesByID;
    if (_bReadEntitiesByID) {
      en_penParent = en_pwoWorld->EntityFromID(iParent);
    } else {
      en_penParent = en_pwoWorld->wo_cenAllEntities.Pointer(iParent);
    }
    *istr>>en_plRelativeToParent;
    // link to parent
    en_penParent->en_lhChildren.AddTail(en_lnInParent);
  }

  // read the derived class properties from stream
  ReadProperties_t(*istr);

  // if it is a light source
  {CLightSource *pls = GetLightSource();
  if (pls!=NULL) {
    // read the light source layer list
    pls->ls_penEntity = this;
    pls->Read_t(istr);
  }}

  // if it is a field brush
  CFieldSettings *pfs = GetFieldSettings();
  if (pfs!=NULL) {
    // remember its field settings
    ASSERT(en_RenderType == RT_FIELDBRUSH);
    en_pbrBrush->br_pfsFieldSettings = pfs;
  }

  // if entity was predictable
  if (en_ulFlags&ENF_PREDICTABLE) {
    // restore that condition
    en_ulFlags&=~ENF_PREDICTABLE; // have to clear it to be able to set it back
    SetPredictable(TRUE);
  }
}

/*
 * Write to stream.
 */
void CEntity::Write_t( CTStream *ostr) // throw char *
{
  ASSERT(GetFPUPrecision()==FPT_24BIT);

  // write base class data to stream
  ostr->WriteID_t("ENT4");
  SLONG slSize = 0;
  (*ostr)<<en_ulID<<slSize;    // save id and keep space for size
  (*ostr)<<(ULONG &)en_RenderType
         <<en_ulPhysicsFlags
         <<en_ulCollisionFlags
         <<en_ulSpawnFlags
         <<en_ulFlags;
  (*ostr)<<en_mRotation;
  // if this is a brush
  if ( en_RenderType == RT_BRUSH || en_RenderType == RT_FIELDBRUSH) {
    // write brush index in world's brush archive
    (*ostr)<<en_pwoWorld->wo_baBrushes.ba_abrBrushes.Index(en_pbrBrush);
  // if this is a terrain
  } else if ( en_RenderType == RT_TERRAIN) {
    // write brush index in world's brush archive
    (*ostr)<<en_pwoWorld->wo_taTerrains.ta_atrTerrains.Index(en_ptrTerrain);
  // if this is a model
  } else if ( en_RenderType == RT_MODEL || en_RenderType == RT_EDITORMODEL) {
    // write model
    WriteModelObject_t(*ostr, *en_pmoModelObject);
  // if this is ska model
  } else if ( en_RenderType == RT_SKAMODEL || en_RenderType == RT_SKAEDITORMODEL) {
    // write ska model
    WriteModelInstance_t(*ostr, *GetModelInstance());
  // if this is a void
  } else if (en_RenderType == RT_VOID) {
    NOTHING;
  }

  // if the entity has a parent
  if (en_penParent!=NULL) {
    // write the parent pointer and relative offset
    ostr->WriteID_t("PART"); // parent
    INDEX iParent = en_penParent->en_ulID;
    *ostr<<iParent;
    *ostr<<en_plRelativeToParent;
  }

  // write the derived class properties to stream
  WriteProperties_t(*ostr);
  // if it is a light source
  {CLightSource *pls = GetLightSource();
  if (pls!=NULL) {
    // read the light source layer list
    pls->Write_t(ostr);
  }}
}


/* Precache components that might be needed. */
void CEntity::Precache(void)
{
  NOTHING;
}


void CEntity::ChecksumForSync(ULONG &ulCRC, INDEX iExtensiveSyncCheck)
{
  if (iExtensiveSyncCheck>0) {
    CRC_AddLONG(ulCRC, en_ulFlags&~
      (ENF_SELECTED|ENF_INRENDERING|ENF_VALIDSHADINGINFO|ENF_FOUNDINGRIDSEARCH|ENF_WILLBEPREDICTED|ENF_PREDICTABLE));
    CRC_AddLONG(ulCRC, en_ulPhysicsFlags);
    CRC_AddLONG(ulCRC, en_ulCollisionFlags);
    CRC_AddLONG(ulCRC, en_ctReferences);
  }
  CRC_AddLONG(ulCRC, en_RenderType);
  if (iExtensiveSyncCheck>0) {
    CRC_AddLONG(ulCRC, en_ulID);
    CRC_AddFLOAT(ulCRC, en_fSpatialClassificationRadius);
    CRC_AddFLOAT(ulCRC, en_plPlacement.pl_PositionVector(1));
    CRC_AddFLOAT(ulCRC, en_plPlacement.pl_PositionVector(2));
    CRC_AddFLOAT(ulCRC, en_plPlacement.pl_PositionVector(3));
    CRC_AddFLOAT(ulCRC, en_plPlacement.pl_OrientationAngle(1));
    CRC_AddFLOAT(ulCRC, en_plPlacement.pl_OrientationAngle(2));
    CRC_AddFLOAT(ulCRC, en_plPlacement.pl_OrientationAngle(3));

    CRC_AddBlock(ulCRC, (UBYTE*)(void*)&en_mRotation, sizeof(en_mRotation));
  } else {
    CRC_AddLONG(ulCRC, (int)en_plPlacement.pl_PositionVector(1));
    CRC_AddLONG(ulCRC, (int)en_plPlacement.pl_PositionVector(2));
    CRC_AddLONG(ulCRC, (int)en_plPlacement.pl_PositionVector(3));
  }
}

void CEntity::DumpSync_t(CTStream &strm, INDEX iExtensiveSyncCheck)  // throw char *
{
  strm.FPrintF_t("\n---- #%05d ($%05d)----------------\n", 
    en_pwoWorld->wo_cenAllEntities.Index(this), en_pwoWorld->wo_cenEntities.Index(this));
  if (en_ulFlags&ENF_DELETED) {
    strm.FPrintF_t("*** DELETED ***\n");
  }
  strm.FPrintF_t("class: '%s'\n", GetClass()->ec_pdecDLLClass->dec_strName);
  strm.FPrintF_t("name: '%s'\n", (const char *) GetName());
  if (iExtensiveSyncCheck>0) {
    strm.FPrintF_t("en_ulFlags:          0x%08X\n", en_ulFlags&~
      (ENF_SELECTED|ENF_INRENDERING|ENF_VALIDSHADINGINFO|ENF_FOUNDINGRIDSEARCH|ENF_WILLBEPREDICTED|ENF_PREDICTABLE));
    strm.FPrintF_t("en_ulPhysicsFlags:   0x%08X\n", en_ulPhysicsFlags);
    strm.FPrintF_t("en_ulCollisionFlags: 0x%08X\n", en_ulCollisionFlags);
    strm.FPrintF_t("en_ctReferences: %d\n", en_ctReferences);
  }
  strm.FPrintF_t("en_RenderType: %d\n", en_RenderType);
  strm.FPrintF_t("en_ulID: 0x%08x\n", en_ulID);
  if (iExtensiveSyncCheck>0) {
    strm.FPrintF_t("en_fSpatialClassificationRadius: %g(%08x)\n", 
      en_fSpatialClassificationRadius, (ULONG&)en_fSpatialClassificationRadius);
  }
  strm.FPrintF_t("placement: %g,%g,%g : %g,%g,%g\n",
    en_plPlacement.pl_PositionVector(1),
    en_plPlacement.pl_PositionVector(2),
    en_plPlacement.pl_PositionVector(3),
    en_plPlacement.pl_OrientationAngle(1),
    en_plPlacement.pl_OrientationAngle(2),
    en_plPlacement.pl_OrientationAngle(3));
  if (iExtensiveSyncCheck>0) {
    strm.FPrintF_t("placement raw:\n %08X %08X %08X\n %08X %08X %08X\n",
      (ULONG&)en_plPlacement.pl_PositionVector(1),
      (ULONG&)en_plPlacement.pl_PositionVector(2),
      (ULONG&)en_plPlacement.pl_PositionVector(3),
      (ULONG&)en_plPlacement.pl_OrientationAngle(1),
      (ULONG&)en_plPlacement.pl_OrientationAngle(2),
      (ULONG&)en_plPlacement.pl_OrientationAngle(3));
    strm.FPrintF_t("matrix:\n %g %g %g\n %g %g %g\n %g %g %g\n",
      en_mRotation(1,1), en_mRotation(1,2), en_mRotation(1,3),
      en_mRotation(2,1), en_mRotation(2,2), en_mRotation(2,3),
      en_mRotation(3,1), en_mRotation(3,2), en_mRotation(3,3));
    strm.FPrintF_t("matrix raw:\n %08X %08X %08X\n %08X %08X %08X\n %08X %08X %08X\n",
      (ULONG&)en_mRotation(1,1), (ULONG&)en_mRotation(1,2), (ULONG&)en_mRotation(1,3),
      (ULONG&)en_mRotation(2,1), (ULONG&)en_mRotation(2,2), (ULONG&)en_mRotation(2,3),
      (ULONG&)en_mRotation(3,1), (ULONG&)en_mRotation(3,2), (ULONG&)en_mRotation(3,3));
    if( en_pciCollisionInfo == NULL) {
      strm.FPrintF_t("Collision info NULL\n");
    } else if (en_RenderType==RT_BRUSH || en_RenderType==RT_FIELDBRUSH) {
      strm.FPrintF_t("Collision info: Brush entity\n");
    } else {
      strm.FPrintF_t("Collision info:\n");
      strm.FPrintF_t("Min height, Max height: %g, %g\n",
        en_pciCollisionInfo->ci_fMinHeight, en_pciCollisionInfo->ci_fMaxHeight);
      strm.FPrintF_t("Handle Y, Handle R: %g, %g\n",
        en_pciCollisionInfo->ci_fHandleY, en_pciCollisionInfo->ci_fHandleR);

      strm.FPrintF_t("Handle Y, Handle R: %g, %g\n",
        en_pciCollisionInfo->ci_fHandleY, en_pciCollisionInfo->ci_fHandleR);
    
      DUMPVECTOR(en_pciCollisionInfo->ci_boxCurrent.Min());
      DUMPVECTOR(en_pciCollisionInfo->ci_boxCurrent.Max());
      DUMPLONG(en_pciCollisionInfo->ci_ulFlags);
    }
  }
}


// get a pseudo-random number (safe for network gaming)
ULONG CEntity::IRnd(void) 
{
  return ((_pNetwork->ga_sesSessionState.Rnd()>>(31-16))&0xFFFF);
}


FLOAT CEntity::FRnd(void)
{
  return ((_pNetwork->ga_sesSessionState.Rnd()>>(31-24))&0xFFFFFF)/FLOAT(0xFFFFFF);
}



// returns ammount of memory used by entity
SLONG CEntity::GetUsedMemory(void)
{
  // initial size
  SLONG slUsedMemory = sizeof(CEntity);

  // add relations
  slUsedMemory += en_rdSectors.Count() * sizeof(CRelationLnk);

  // add allocated memory for model type (if any)
  switch( en_RenderType) {
  case CEntity::RT_MODEL:
  case CEntity::RT_EDITORMODEL:
    slUsedMemory += en_pmoModelObject->GetUsedMemory();
    break;
  case CEntity::RT_SKAMODEL:
  case CEntity::RT_SKAEDITORMODEL:
    slUsedMemory += en_pmiModelInstance->GetUsedMemory();
  default:
    break;
  }

  // add shading info (if any)
  if( en_psiShadingInfo !=NULL) {
    slUsedMemory += sizeof(CShadingInfo);
  }
  // add collision info (if any)
  if( en_pciCollisionInfo!=NULL) {
    slUsedMemory += sizeof(CCollisionInfo) + (en_pciCollisionInfo->ci_absSpheres.sa_Count * sizeof(CMovingSphere));
  }
  // add last positions memory (if any)
  if( en_plpLastPositions!=NULL) {
    slUsedMemory += sizeof(CLastPositions) + (en_plpLastPositions->lp_avPositions.sa_Count * sizeof(FLOAT3D));
  }

  // done
  return slUsedMemory;
}



/* Get pointer to entity property from its packed identifier. */
class CEntityProperty *CEntity::PropertyForTypeAndID(ULONG ulType, ULONG ulID)
{
  return en_pecClass->PropertyForTypeAndID(ulType, ulID);
}

/* Get pointer to entity component from its packed identifier. */
class CEntityComponent *CEntity::ComponentForTypeAndID(ULONG ulType, ULONG ulID)
{
  return en_pecClass->ComponentForTypeAndID((enum EntityComponentType)ulType, ulID);
}

/* Get pointer to entity property from its name. */
class CEntityProperty *CEntity::PropertyForName(const CTString &strPropertyName)
{
  return en_pecClass->PropertyForName(strPropertyName);
}
 
/* Create a new entity of given class in this world. */
CEntity *CEntity::CreateEntity(const CPlacement3D &plPlacement, SLONG idClass)
{
  CEntityComponent *pecClassComponent = en_pecClass->ComponentForTypeAndID(
    ECT_CLASS, idClass);
  return en_pwoWorld->CreateEntity(plPlacement, pecClassComponent->ec_pecEntityClass);
}



/////////////////////////////////////////////////////////////////////
// CLiveEntity

/*
 * Constructor.
 */
CLiveEntity::CLiveEntity(void)
{
  en_fHealth = 0;
}

/* Copy entity from another entity of same class. */
void CLiveEntity::Copy(CEntity &enOther, ULONG ulFlags)
{
  CEntity::Copy(enOther, ulFlags);
  CLiveEntity *plenOther = (CLiveEntity *)(&enOther);
  en_fHealth = plenOther->en_fHealth;
}
/* Read from stream. */
void CLiveEntity::Read_t( CTStream *istr) // throw char *
{
  CEntity::Read_t(istr);
  (*istr)>>en_fHealth;
}
/* Write to stream. */
void CLiveEntity::Write_t( CTStream *ostr) // throw char *
{
  CEntity::Write_t(ostr);
  (*ostr)<<en_fHealth;
}

// apply some damage to the entity (see event EDamage for more info)
void CLiveEntity::ReceiveDamage(CEntity *penInflictor, enum DamageType dmtType,
  FLOAT fDamage, const FLOAT3D &vHitPoint, const FLOAT3D &vDirection)
{
  CEntityPointer penThis = this;  // keep this entity alive during this function

  // reduce your health
  en_fHealth-=fDamage;

  // throw an event that you are damaged
  EDamage eDamage;
  eDamage.penInflictor = penInflictor;
  eDamage.vDirection   = vDirection;
  eDamage.vHitPoint    = vHitPoint;
  eDamage.fAmount      = fDamage;
  eDamage.dmtType      = dmtType;
  SendEvent(eDamage);

  // if health reached zero
  if (en_fHealth<=0) {
    // throw an event that you have died
    EDeath eDeath;
    eDeath.eLastDamage = eDamage;
    SendEvent(eDeath);
  }
}
/////////////////////////////////////////////////////////////////////
// CRationalEntity

/*
 * Constructor.
 */
CRationalEntity::CRationalEntity(void)
{
}

/* Calculate physics for moving. */
void CRationalEntity::ClearMovingTemp(void)
{
}
void CRationalEntity::PreMoving(void)
{
}
void CRationalEntity::DoMoving(void)
{
}
void CRationalEntity::PostMoving(void)
{
}
// create a checksum value for sync-check
void CRationalEntity::ChecksumForSync(ULONG &ulCRC, INDEX iExtensiveSyncCheck)
{
  CEntity::ChecksumForSync(ulCRC, iExtensiveSyncCheck);

  if (iExtensiveSyncCheck>0) {
    CRC_AddFLOAT(ulCRC, en_timeTimer);
    CRC_AddLONG(ulCRC, en_stslStateStack.Count());
  }
  if (iExtensiveSyncCheck>0) {
  }
}

// dump sync data to text file
void CRationalEntity::DumpSync_t(CTStream &strm, INDEX iExtensiveSyncCheck)  // throw char *
{
  CEntity::DumpSync_t(strm, iExtensiveSyncCheck);
  if (iExtensiveSyncCheck>0) {
    strm.FPrintF_t("en_timeTimer:  %g(%08x)\n", en_timeTimer, (ULONG&)en_timeTimer);
    strm.FPrintF_t("en_stslStateStack.Count(): %d\n", en_stslStateStack.Count());
  }
  strm.FPrintF_t("en_fHealth:    %g(%08x)\n", en_fHealth, (ULONG&)en_fHealth);
}

/* Copy entity from another entity of same class. */
void CRationalEntity::Copy(CEntity &enOther, ULONG ulFlags)
{
  CLiveEntity::Copy(enOther, ulFlags);
  if (!(ulFlags&COPY_REINIT)) {
    CRationalEntity *prenOther = (CRationalEntity *)(&enOther);
    en_timeTimer = prenOther->en_timeTimer;
    en_stslStateStack = prenOther->en_stslStateStack;
    if (prenOther->en_lnInTimers.IsLinked()) {
      en_pwoWorld->AddTimer(this);
    }
  }
}
/* Read from stream. */
void CRationalEntity::Read_t( CTStream *istr) // throw char *
{
  CLiveEntity::Read_t(istr);
  (*istr)>>en_timeTimer;
  // if waiting for thinking
  if (en_timeTimer!=THINKTIME_NEVER) {
    // add to list of thinkers
    en_pwoWorld->AddTimer(this);
  }
  // read the state stack
  en_stslStateStack.Clear();
  en_stslStateStack.SetAllocationStep(STATESTACK_ALLOCATIONSTEP);
  INDEX ctStates;
  (*istr)>>ctStates;
  for (INDEX iState=0; iState<ctStates; iState++) {
    (*istr)>>en_stslStateStack.Push();
  }

}
/* Write to stream. */
void CRationalEntity::Write_t( CTStream *ostr) // throw char *
{
  CLiveEntity::Write_t(ostr);
  // if not currently waiting for thinking
  if (!en_lnInTimers.IsLinked()) {
    // set dummy thinking time as a flag for later loading
    en_timeTimer = THINKTIME_NEVER;
  }
  (*ostr)<<en_timeTimer;
  // write the state stack
  (*ostr)<<en_stslStateStack.Count();
  for(INDEX iState=0; iState<en_stslStateStack.Count(); iState++) {
    (*ostr)<<en_stslStateStack[iState];
  }
}

/*
 * Set next timer event to occur at given moment time.
 */
void CRationalEntity::SetTimerAt(TIME timeAbsolute)
{
  // must never set think back in time, except for special 'never' time
  ASSERTMSG(timeAbsolute>=_pTimer->CurrentTick() ||
    timeAbsolute==THINKTIME_NEVER, "Do not SetThink() back in time!");
  // set the timer
  en_timeTimer = timeAbsolute;

  // add to world's list of timers if neccessary
  if (en_timeTimer != THINKTIME_NEVER) {
    en_pwoWorld->AddTimer(this);
  } else {
    if (en_lnInTimers.IsLinked()) {
      en_lnInTimers.Remove();
    }
  }
}

/*
 * Set next timer event to occur after given time has elapsed.
 */
void CRationalEntity::SetTimerAfter(TIME timeDelta)
{
  // set the execution for the moment that is that much ahead of the current tick
  SetTimerAt(_pTimer->CurrentTick()+timeDelta);
}

/* Cancel eventual pending timer. */
void CRationalEntity::UnsetTimer(void)
{
  en_timeTimer = THINKTIME_NEVER;
  if (en_lnInTimers.IsLinked()) {
    en_lnInTimers.Remove();
  }
}

/*
 * Unwind stack to a given state.
 */
void CRationalEntity::UnwindStack(SLONG slThisState)
{
  // for each state on the stack (from top to bottom)
  for(INDEX iStateInStack=en_stslStateStack.Count()-1; iStateInStack>=0; iStateInStack--) {
    // if it is the state
    if (en_stslStateStack[iStateInStack]==slThisState) {
      // unwind to it
      en_stslStateStack.PopUntil(iStateInStack);
      return;
    }
  }
  // the state must be on the stack
  ASSERTALWAYS("Unwinding to unexisting state!");
}

/*
 * Jump to a new state.
 */
void CRationalEntity::Jump(SLONG slThisState, SLONG slTargetState, BOOL bOverride, const CEntityEvent &eeInput)
{
  // unwind the stack to this state
  UnwindStack(slThisState);
  // set the new topmost state
  if (bOverride) {
    slTargetState = en_pecClass->ec_pdecDLLClass->GetOverridenState(slTargetState);
  }
  en_stslStateStack[en_stslStateStack.Count()-1] = slTargetState;
  // handle the given event in the new state
  HandleEvent(eeInput);
};
/*
 * Call a subautomaton.
 */
void CRationalEntity::Call(SLONG slThisState, SLONG slTargetState, BOOL bOverride, const CEntityEvent &eeInput)
{
  // unwind the stack to this state
  UnwindStack(slThisState);
  // push the new state to stack
  if (bOverride) {
    slTargetState = en_pecClass->ec_pdecDLLClass->GetOverridenState(slTargetState);
  }
  en_stslStateStack.Push() = slTargetState;
  // handle the given event in the new state
  HandleEvent(eeInput);
};
/*
 * Return from a subautomaton.
 */
void CRationalEntity::Return(SLONG slThisState, const CEntityEvent &eeReturn)
{
  // unwind the stack to this state
  UnwindStack(slThisState);
  // pop one state from the stack
  en_stslStateStack.PopUntil(en_stslStateStack.Count()-2);
  // handle the given event in the new topmost state
  HandleEvent(eeReturn);
};

// print stack to debug output
const char *CRationalEntity::PrintStackDebug(void)
{
  _RPT2(_CRT_WARN, "-- stack of '%s'@%gs\n", (const char *) GetName(), _pTimer->CurrentTick());

  INDEX ctStates = en_stslStateStack.Count();
  for(INDEX iState=ctStates-1; iState>=0; iState--) {
    SLONG slState = en_stslStateStack[iState];
    _RPT2(_CRT_WARN, "0x%08x %s\n", slState, 
      en_pecClass->ec_pdecDLLClass->HandlerNameForState(slState));
  }
  _RPT0(_CRT_WARN, "----\n");
  return "ok";
}

/*
 * Handle an event - return false if event was not handled.
 */
BOOL CRationalEntity::HandleEvent(const CEntityEvent &ee)
{
  // for each state on the stack (from top to bottom)
  for(INDEX iStateInStack=en_stslStateStack.Count()-1; iStateInStack>=0; iStateInStack--) {
    // try to find a handler in that state
    pEventHandler pehHandler =
      HandlerForStateAndEvent(en_stslStateStack[iStateInStack], ee.ee_slEvent);
    // if there is a handler
    if (pehHandler!=NULL) {
      // call the function
      BOOL bHandled = (this->*pehHandler)(ee);
      // if the event was successfully handled
      if (bHandled) {
        // return that it was handled
        return TRUE;
      }
    }
  }

  // if no transition was found, the event was not handled
  return FALSE;
}

/*
 * Called after creating and setting its properties.
 */
void CRationalEntity::OnInitialize(const CEntityEvent &eeInput)
{
  // make sure entity doesn't destroy itself during intialization
  CEntityPointer penThis = this;

  // do not think
  en_timeTimer = THINKTIME_NEVER;
  if (en_lnInTimers.IsLinked()) {
    en_lnInTimers.Remove();
  }

  // initialize state stack
  en_stslStateStack.Clear();
  en_stslStateStack.SetAllocationStep(STATESTACK_ALLOCATIONSTEP);

  en_stslStateStack.Push() = 1;   // start state is always state with number 1

  // call the main function of the entity
  HandleEvent(eeInput);
}

/* Called before releasing entity. */
void CRationalEntity::OnEnd(void)
{
  // cancel eventual pending timer
  UnsetTimer();
}

