/* 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include // 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 && iPlayerga_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)+EPSILONvOnPlane(2)) || (boxPolygon.Max()(2)+EPSILONvOnPlane(3)) || (boxPolygon.Max()(3)+EPSILONbpe_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 &apbpo = ((CMovableEntity*)this)->en_apbpoNearPolygons; for(INDEX iPolygon=0; iPolygonbsc_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<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; iSphereGetFirstMip(); // 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); DOUBLEobbox3D boxdEntity = FLOATtoDOUBLE(boxEntity); // 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( FLOATtoDOUBLE(vSphereCenter), FLOATtoDOUBLE(fSphereRadius))>=0) { // if the box is inside the sector if (itbsc->bsc_bspBSPTree.TestBox(boxdEntity)>=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); DOUBLEobbox3D oboxdEntity = FLOATtoDOUBLE(oboxEntity); 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 &apbpo = pen->en_apbpoNearPolygons; // for each cached polygon for(INDEX iPolygon=0; iPolygonbpo_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( FLOATtoDOUBLE(vSphereCenter), FLOATtoDOUBLE(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(oboxdEntity)>=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 _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 &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 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 &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; iPoslp_avPositions[iPos] = vNow; } } while(en_plpLastPositions->lp_tmLastAddedAddPosition(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(FLOATtoDOUBLE(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 (fDistanceci_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 (fDms_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 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 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 apenNearEntities; en_pwoWorld->FindEntitiesNearBox(en_pciCollisionInfo->ci_boxCurrent, apenNearEntities); // for each of the found entities {for(INDEX ienFound=0; ienFoundAddToMovers(); } }} 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)<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)<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<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)<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©_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>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)<=_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(); }