//----------------------------------------------------------------------------- // Purpose: // Input : dwNodeID - // Output : CMapEntity * //----------------------------------------------------------------------------- CMapEntity *CMapPath::CreateEntityForNode(DWORD dwNodeID) { int iIndex; CMapPathNode *pNode = NodeForID(dwNodeID, &iIndex); if (pNode == NULL) { return NULL; // no node, no entity! } CMapEntity *pEntity = new CMapEntity; for (int k = pNode->kv.GetFirst(); k != pNode->kv.GetInvalidIndex(); k=pNode->kv.GetNext( k ) ) { pEntity->SetKeyValue(pNode->kv.GetKey(k), pNode->kv.GetValue(k)); } // store target/targetname properties: CString str; str.Format("%s%02d", m_szName, iIndex); pEntity->SetKeyValue("targetname", str); int iNext = iIndex + 1; if(iNext != -1) { str.Format("%s%02d", m_szName, iNext); pEntity->SetKeyValue("target", str); } pEntity->SetClass(m_szClass); return pEntity; }
//----------------------------------------------------------------------------- // Purpose: Called when an object that we depend on has changed. //----------------------------------------------------------------------------- void CMapKeyFrame::OnNotifyDependent(CMapClass *pObject, Notify_Dependent_t eNotifyType) { CMapClass::OnNotifyDependent(pObject, eNotifyType); if ((pObject == m_pAnimator) && (eNotifyType == Notify_Removed)) { SetAnimator(NULL); } // // If our next keyframe was deleted, try to link to the one after it. // if ((pObject == m_pNextKeyFrame) && (eNotifyType == Notify_Removed)) { CMapEntity *pNextParent = m_pNextKeyFrame->GetParentEntity(); CMapEntity *pParent = GetParentEntity(); if ( pNextParent && pParent ) { const char *szNext = pNextParent->GetKeyValue("NextKey"); pParent->SetKeyValue("NextKey", szNext); } } m_bRebuildPath = true; }
//----------------------------------------------------------------------------- // Purpose: Updates the cached pointers to our start and end entities by looking // for them in the given world. // Input : pWorld - World to search. //----------------------------------------------------------------------------- void CMapLine::UpdateDependencies(CMapWorld *pWorld, CMapClass *pObject) { CMapClass::UpdateDependencies(pWorld, pObject); if (pWorld == NULL) { return; } CMapEntity *pEntity = dynamic_cast <CMapEntity *> (m_pParent); Assert(pEntity != NULL); if (pEntity != NULL) { const char *pszValue = pEntity->GetKeyValue(m_szStartValueKey); m_pStartEntity = (CMapEntity *)UpdateDependency(m_pStartEntity, pWorld->FindChildByKeyValue(m_szStartKey, pszValue)); if (m_szEndValueKey[0] != '\0') { pszValue = pEntity->GetKeyValue(m_szEndValueKey); m_pEndEntity = (CMapEntity *)UpdateDependency(m_pEndEntity, pWorld->FindChildByKeyValue(m_szEndKey, pszValue)); } else { // We don't have an end entity specified, use our parent as the end point. m_pEndEntity = (CMapEntity *)UpdateDependency(m_pEndEntity, GetParent()); } BuildLine(); } }
//----------------------------------------------------------------------------- // Purpose: // Input : pError - //----------------------------------------------------------------------------- static void FixDuplicateKeys(MapError *pError) { // find duplicate keys in keyvalues and kill them CMapEntity *pEntity = (CMapEntity *)pError->pObjects[0]; int nKeyValues = pEntity->GetKeyValueCount(); DoAgain: for (int i = 0; i < nKeyValues; i++) { for (int j = 0; j < nKeyValues; j++) { if (i == j) { continue; } if (!strcmpi(pEntity->GetKey(i), pEntity->GetKey(j))) { pEntity->RemoveKey(i); //--kv.nkv; goto DoAgain; } } } }
//----------------------------------------------------------------------------- // Purpose: Builds a new value string for our parent's facelist key from our // current list of faces and submits the keyvalue to our parent for // storage. //----------------------------------------------------------------------------- void CMapSideList::UpdateParentKey(void) { char szValue[KEYVALUE_MAX_VALUE_LENGTH]; CMapWorld::FaceID_FaceListsToString(szValue, sizeof(szValue), &m_Faces, NULL); CMapEntity *pEntity = dynamic_cast<CMapEntity *>(Parent); if (pEntity != NULL) { pEntity->NotifyChildKeyChanged(this, m_szKeyName, szValue); } }
//----------------------------------------------------------------------------- // Purpose: Called after the entire map has been loaded. This allows the object // to perform any linking with other map objects or to do other operations // that require all world objects to be present. // Input : pWorld - The world that we are in. //----------------------------------------------------------------------------- void CMapSideList::PostloadWorld(CMapWorld *pWorld) { CMapClass::PostloadWorld(pWorld); // // Generate our list of faces from our parent's keyvalue. // CMapEntity *pEntity = dynamic_cast<CMapEntity *>(Parent); if (pEntity != NULL) { const char *pszValue = pEntity->GetKeyValue(m_szKeyName); if (pszValue != NULL) { BuildFaceListForValue(pszValue, pWorld); } } }
//----------------------------------------------------------------------------- // Purpose: notifies the keyframe that it has been cloned // inserts the clone into the correct place in the keyframe list // Input : *pClone - //----------------------------------------------------------------------------- void CMapKeyFrame::OnClone( CMapClass *pClone, CMapWorld *pWorld, const CMapObjectList &OriginalList, CMapObjectList &NewList ) { CMapClass::OnClone( pClone, pWorld, OriginalList, NewList ); CMapKeyFrame *pNewKey = dynamic_cast<CMapKeyFrame*>( pClone ); Assert( pNewKey != NULL ); if ( !pNewKey ) return; CMapEntity *pEntity = dynamic_cast<CMapEntity*>( m_pParent ); CMapEntity *pNewEntity = dynamic_cast<CMapEntity*>( pClone->GetParent() ); // insert the newly created keyframe into the sequence // point the clone's next at what we were pointing at const char *nextKey = pEntity->GetKeyValue( "NextKey" ); if ( nextKey ) { pNewEntity->SetKeyValue( "NextKey", nextKey ); } // create a new targetname for the clone char newName[128]; const char *oldName = pEntity->GetKeyValue( "targetname" ); if ( !oldName || oldName[0] == 0 ) oldName = "keyframe"; pWorld->GenerateNewTargetname( oldName, newName, sizeof( newName ), true, NULL ); pNewEntity->SetKeyValue( "targetname", newName ); // point the current keyframe at the clone pEntity->SetKeyValue( "NextKey", newName ); }
//----------------------------------------------------------------------------- // Purpose: // Input : pObject - // FindObject - // Output : Returns true if the object matches the search criteria, false if not. //----------------------------------------------------------------------------- bool FindCheck(CMapClass *pObject, FindObject_t &FindObject) { CMapEntity *pEntity = dynamic_cast <CMapEntity *>(pObject); if (!pEntity) { return false; } if (FindObject.bVisiblesOnly && !pObject->IsVisible()) { return false; } // // Search keyvalues. // int nKeyCount = pEntity->GetKeyValueCount(); for (int i = 0; i < nKeyCount; i++) { const char *pszValue = pEntity->GetKeyValue(i); if (pszValue && MatchString(pszValue, FindObject)) { return true; } } // // Search connections. // POSITION pos = pEntity->Connections_GetHeadPosition(); while (pos) { CEntityConnection *pConn = pEntity->Connections_GetNext(pos); if (pConn) { if (MatchString(pConn->GetTargetName(), FindObject) || MatchString(pConn->GetParam(), FindObject)) { return true; } } } return false; }
//----------------------------------------------------------------------------- // Purpose: Checks for node entities with the same node ID. //----------------------------------------------------------------------------- static void CheckDuplicateNodeIDs(CListBox *pList, CMapWorld *pWorld) { EnumChildrenPos_t pos; CMapClass *pChild = pWorld->GetFirstDescendent(pos); while (pChild != NULL) { CMapEntity *pEntity = dynamic_cast<CMapEntity *>(pChild); if (pEntity && pEntity->IsNodeClass()) { if (FindDuplicateNodeID(pEntity, pWorld)) { AddError(pList, ErrorDuplicateNodeIDs, (DWORD)pWorld, pEntity); } } pChild = pWorld->GetNextDescendent(pos); } }
//----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CMapPointHandle::UpdateParentKey(void) { // Snap to prevent error creep. for (int i = 0; i < 3; i++) { m_Origin[i] = rint(m_Origin[i] / 0.01f) * 0.01f; } if (m_szKeyName[0]) { CMapEntity *pEntity = dynamic_cast <CMapEntity *> (m_pParent); if (pEntity != NULL) { char szValue[KEYVALUE_MAX_VALUE_LENGTH]; sprintf(szValue, "%g %g %g", (double)m_Origin.x, (double)m_Origin.y, (double)m_Origin.z); pEntity->NotifyChildKeyChanged(this, m_szKeyName, szValue); } } }
//----------------------------------------------------------------------------- // Purpose: // Input : pCopy - // pSourceWorld - // pDestWorld - // OriginalList - // NewList - //----------------------------------------------------------------------------- void CMapSideList::OnPaste(CMapClass *pCopyObject, CMapWorld *pSourceWorld, CMapWorld *pDestWorld, CMapObjectList &OriginalList, CMapObjectList &NewList) { CMapSideList *pCopy = (CMapSideList *)pCopyObject; // // HACK: This is kinda nasty. Build a list of face IDs from our parent keyvalue so // we can remap them to faces in the clipboard. // CMapEntity *pParent = dynamic_cast <CMapEntity *>(pCopy->GetParent()); const char *pszValue = pParent->GetKeyValue(m_szKeyName); if (pszValue != NULL) { char szVal[KEYVALUE_MAX_VALUE_LENGTH]; strcpy(szVal, pszValue); char *psz = strtok(szVal, " "); while (psz != NULL) { // // The substring should now be a single face ID. Get the corresponding // face and add it to the list. // int nFaceID = atoi(psz); CMapFace *pFace = FindFaceIDInList(nFaceID, OriginalList); if (pFace != NULL) { pCopy->m_Faces.AddToTail(pFace); } // // Get the next substring. // psz = strtok(NULL, " "); } } // // Now replace all faces with their new counterparts, as in Clone. // ReplaceFacesInCopy(pCopy, OriginalList, NewList); }
//----------------------------------------------------------------------------- // Purpose: // Input : iInterpolator - // Output : IPositionInterpolator //----------------------------------------------------------------------------- IPositionInterpolator* CMapKeyFrame::SetupPositionInterpolator( int iInterpolator ) { if( iInterpolator != m_iPositionInterpolator ) { if( m_pPositionInterpolator ) m_pPositionInterpolator->Release(); m_pPositionInterpolator = Motion_GetPositionInterpolator( iInterpolator ); m_iPositionInterpolator = iInterpolator; // Feed keys.. CMapEntity *pEnt = GetParentEntity(); if( pEnt ) { for ( int i=pEnt->GetFirstKeyValue(); i != pEnt->GetInvalidKeyValue(); i=pEnt->GetNextKeyValue( i ) ) { m_pPositionInterpolator->ProcessKey( pEnt->GetKey( i ), pEnt->GetKeyValue( i ) ); } } } return m_pPositionInterpolator; }
//----------------------------------------------------------------------------- // Purpose: Returns true if there is another node entity in the world with the // same node ID as the given entity. // Input : pNode - // pWorld - //----------------------------------------------------------------------------- bool FindDuplicateNodeID(CMapEntity *pNode, CMapWorld *pWorld) { EnumChildrenPos_t pos; CMapClass *pChild = pWorld->GetFirstDescendent(pos); while (pChild != NULL) { CMapEntity *pEntity = dynamic_cast<CMapEntity *>(pChild); if (pEntity && (pEntity != pNode) && pEntity->IsNodeClass()) { int nNodeID1 = pNode->GetNodeID(); int nNodeID2 = pEntity->GetNodeID(); if ((nNodeID1 != 0) && (nNodeID2 != 0) && (nNodeID1 == nNodeID2)) { return true; } } pChild = pWorld->GetNextDescendent(pos); } return false; }
//----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CMapSweptPlayerHull::UpdateParentKey(void) { CMapEntity *pEntity = dynamic_cast <CMapEntity *> (m_pParent); if (pEntity != NULL) { Vector vecOrigin1; Vector vecOrigin2; m_Point[0]->GetOrigin(vecOrigin1); m_Point[1]->GetOrigin(vecOrigin2); vecOrigin1 -= playerFixup; vecOrigin2 -= playerFixup; PostUpdate(Notify_Changed); char szValue[KEYVALUE_MAX_VALUE_LENGTH]; sprintf(szValue, "%g %g %g", (double)vecOrigin1.x, (double)vecOrigin1.y, (double)vecOrigin1.z ); pEntity->NotifyChildKeyChanged(this, "point0", szValue); pEntity->NotifyChildKeyChanged(this, "origin", szValue); sprintf(szValue, "%g %g %g", (double)vecOrigin2.x, (double)vecOrigin2.y, (double)vecOrigin2.z); pEntity->NotifyChildKeyChanged(this, "point1", szValue); } }
//----------------------------------------------------------------------------- // Purpose: // Input : pError - //----------------------------------------------------------------------------- static void FixUnusedKeyvalues(MapError *pError) { CMapEntity *pEntity = (CMapEntity*) pError->pObjects[0]; GDclass *pClass = pEntity->GetClass(); if (!pClass) { return; } int nKeyValues = pEntity->GetKeyValueCount(); for (int i = nKeyValues - 1; i >= 0; i--) { if (pClass->VarForName(pEntity->GetKey(i)) == NULL) { pEntity->DeleteKeyValue(pEntity->GetKey(i)); } } }
void Marker3D::CreateMapObject(CMapView2D *pView) { CMapWorld *pWorld = m_pDocument->GetMapWorld(); CMapClass *pobj = NULL; // // Handle prefab creation. // if (GetMainWnd()->m_ObjectBar.IsEntityToolCreatingPrefab()) { GetHistory()->MarkUndoPosition(m_pDocument->Selection_GetList(), "New Prefab"); CMapClass *pPrefabObject = GetMainWnd()->m_ObjectBar.BuildPrefabObjectAtPoint(m_vecPos); if (pPrefabObject == NULL) { pView->MessageBox("Unable to load prefab", "Error", MB_OK); SetEmpty(); return; } m_pDocument->ExpandObjectKeywords(pPrefabObject, pWorld); m_pDocument->AddObjectToWorld(pPrefabObject); GetHistory()->KeepNew(pPrefabObject); pobj = pPrefabObject; } // // Handle entity creation. // else if (GetMainWnd()->m_ObjectBar.IsEntityToolCreatingEntity()) { GetHistory()->MarkUndoPosition(m_pDocument->Selection_GetList(), "New Entity"); CMapEntity *pEntity = new CMapEntity; pEntity->SetPlaceholder(TRUE); pEntity->SetOrigin(m_vecPos); pEntity->SetClass(CObjectBar::GetDefaultEntityClass()); m_pDocument->AddObjectToWorld(pEntity); pobj = pEntity; GetHistory()->KeepNew(pEntity); } m_pDocument->ToolUpdateViews(CMapView2D::updEraseTool); // // Select the new object. // m_pDocument->SelectObject(pobj, CMapDoc::scClear | CMapDoc::scSelect | CMapDoc::scUpdateDisplay); // // Update all the views. // UpdateBox ub; CMapObjectList ObjectList; ObjectList.AddTail(pobj); ub.Objects = &ObjectList; Vector mins; Vector maxs; pobj->GetRender2DBox(mins, maxs); ub.Box.SetBounds(mins, maxs); m_pDocument->UpdateAllViews(NULL, MAPVIEW_UPDATE_OBJECTS, &ub); SetEmpty(); m_pDocument->SetModifiedFlag(); }
//----------------------------------------------------------------------------- // Purpose: Fixes duplicate node IDs by assigning the entity a unique node ID. // Input : pError - Holds the world and the entity that is in error. //----------------------------------------------------------------------------- static void FixDuplicateNodeIDs(MapError *pError) { CMapEntity *pEntity = (CMapEntity *)pError->pObjects[0]; CMapWorld *pWorld = (CMapWorld *)pError->dwExtra; pEntity->AssignNodeID(); }
//----------------------------------------------------------------------------- // Purpose: creates a new keyframe at the specified time // Input : time - // Output : Returns true on success, false on failure. //----------------------------------------------------------------------------- CMapEntity *CMapAnimator::CreateNewKeyFrame( float time ) { // work out where we are in the animation CMapKeyFrame *key; CMapKeyFrame *pPrevKey; float partialTime = GetKeyFramesAtTime( time, key, pPrevKey ); CMapEntity *pCurrentEnt = dynamic_cast<CMapEntity*>( key->Parent ); // check to see if we're direction on a key frame Vector posOffset( 0, 0, 0 ); if ( partialTime == 0 ) { // create this new key frame slightly after the current one, and offset posOffset[0] = 64; } // get our orientation and position at this time Vector vOrigin; QAngle angles; Quaternion qAngles; GetAnimationAtTime( key, pPrevKey, partialTime, vOrigin, qAngles, m_iPositionInterpolator, m_iRotationInterpolator ); QuaternionAngles( qAngles, angles ); // create the new map entity CMapDoc *pDoc = CMapDoc::GetActiveMapDoc(); CMapWorld *pWorld = pDoc->GetMapWorld(); CMapEntity *pNewEntity = new CMapEntity; Vector newPos; VectorAdd( vOrigin, posOffset, newPos ); pNewEntity->SetPlaceholder( TRUE ); pNewEntity->SetOrigin( newPos ); pNewEntity->SetClass( "keyframe_track" ); char buf[128]; sprintf( buf, "%f %f %f", angles[0], angles[1], angles[2] ); pNewEntity->SetKeyValue( "angles", buf ); // link it into the keyframe list // take over this existing next keyframe pointer const char *nextKeyName = pCurrentEnt->GetKeyValue( "NextKey" ); if ( nextKeyName ) { pNewEntity->SetKeyValue( "NextKey", nextKeyName ); } // create a new unique name for this ent char newName[128]; const char *oldName = pCurrentEnt->GetKeyValue( "targetname" ); if ( !oldName || oldName[0] == 0 ) oldName = "keyframe"; CMapEntity::GenerateNewTargetname( oldName, newName, 127 ); pNewEntity->SetKeyValue( "targetname", newName ); // point the current entity at the newly created one pCurrentEnt->SetKeyValue( "NextKey", newName ); // copy any relevant values const char *keyValue = pCurrentEnt->GetKeyValue( "parentname" ); if ( keyValue ) pNewEntity->SetKeyValue( "parentname", keyValue ); keyValue = pCurrentEnt->GetKeyValue( "MoveSpeed" ); if ( keyValue ) pNewEntity->SetKeyValue( "MoveSpeed", keyValue ); return(pNewEntity); }
//----------------------------------------------------------------------------- // Purpose: Rebuilds the line path between keyframe entities // samples the interpolator function to get an approximation of the curve //----------------------------------------------------------------------------- void CMapAnimator::RebuildPath( void ) { CMapClass *pWorld = GetWorldObject( this ); if ( !pWorld ) { // Sometimes the object isn't linked back into the world yet when RebuildPath() // is called... we will be get called again when needed, but may cause an incorrect // (linear-only) path to get drawn temporarily. return; } // // Build the path forward from the head. Keep a list of nodes we've visited to // use in detecting circularities. // CMapObjectList VisitedList; CMapKeyFrame *pCurKey = this; while ( pCurKey != NULL ) { VisitedList.AddTail( pCurKey ); // // Attach ourselves as this keyframe's animator. // pCurKey->SetAnimator( this ); // // Get the entity parent of this keyframe so we can query keyvalues. // CMapEntity *pCurEnt = dynamic_cast<CMapEntity *>( pCurKey->GetParent() ); if ( !pCurEnt ) { return; } // // Find the next keyframe in the path. // CMapEntity *pNextEnt = pWorld->FindChildByKeyValue( "targetname", pCurEnt->GetKeyValue( "NextKey" ) ); CMapKeyFrame *pNextKey = NULL; if ( pNextEnt ) { pNextKey = pNextEnt->GetChildOfType( ( CMapKeyFrame * )NULL ); pCurKey->SetNextKeyFrame(pNextKey); } else { pCurKey->SetNextKeyFrame( NULL ); } pCurKey = pNextKey; // // If we detect a circularity, stop. // if ( VisitedList.Find( pCurKey ) ) { break; } } // // Now traverse the path again building the spline points, once again checking // the visited list for circularities. // VisitedList.RemoveAll(); pCurKey = this; CMapKeyFrame *pPrevKey = this; while ( pCurKey != NULL ) { VisitedList.AddTail( pCurKey ); pCurKey->BuildPathSegment(pPrevKey); pPrevKey = pCurKey; pCurKey = pCurKey->m_pNextKeyFrame; // // If we detect a circularity, stop. // if ( VisitedList.Find( pCurKey ) ) { break; } } }
//----------------------------------------------------------------------------- // Purpose: // Input : *pView - // nFlags - // point - // Output : Returns true on success, false on failure. //----------------------------------------------------------------------------- bool Marker3D::OnLMouseDown3D(CMapView3D *pView, UINT nFlags, CPoint point) { ULONG ulFace; CMapClass *pObject = pView->NearestObjectAt(point, ulFace); if (pObject != NULL) { CMapSolid *pSolid = dynamic_cast <CMapSolid *> (pObject); if (pSolid == NULL) { // Clicked on a point entity - do nothing. return true; } CMapDoc *pDoc = pView->GetDocument(); // // Build a ray to trace against the face that they clicked on to // find the point of intersection. // Vector Start; Vector End; pView->BuildRay(point, Start, End); Vector HitPos, HitNormal; CMapFace *pFace = pSolid->GetFace(ulFace); if (pFace->TraceLine(HitPos, HitNormal, Start, End)) { if (GetMainWnd()->m_ObjectBar.IsEntityToolCreatingPrefab()) { // // Prefab creation. // pDoc->Snap(HitPos); GetHistory()->MarkUndoPosition(pDoc->Selection_GetList(), "New Prefab"); // Get prefab object CMapClass *pPrefabObject = GetMainWnd()->m_ObjectBar.BuildPrefabObjectAtPoint(HitPos); // // Add prefab to the world. // CMapWorld *pWorld = pDoc->GetMapWorld(); pDoc->ExpandObjectKeywords(pPrefabObject, pWorld); pDoc->AddObjectToWorld(pPrefabObject); GetHistory()->KeepNew(pPrefabObject); // // Select the new object. // pDoc->SelectObject(pPrefabObject, CMapDoc::scClear | CMapDoc::scSelect | CMapDoc::scUpdateDisplay); // // Update world bounds. // UpdateBox ub; CMapObjectList ObjectList; ObjectList.AddTail(pPrefabObject); ub.Objects = &ObjectList; Vector mins; Vector maxs; pPrefabObject->GetRender2DBox(mins, maxs); ub.Box.SetBounds(mins, maxs); pDoc->UpdateAllViews(NULL, MAPVIEW_UPDATE_OBJECTS, &ub); pDoc->SetModifiedFlag(); } else if (GetMainWnd()->m_ObjectBar.IsEntityToolCreatingEntity()) { // // Entity creation. // GetHistory()->MarkUndoPosition(pDoc->Selection_GetList(), "New Entity"); CMapEntity *pEntity = new CMapEntity; pEntity->SetPlaceholder(TRUE); pEntity->SetOrigin(HitPos); pEntity->SetClass(CObjectBar::GetDefaultEntityClass()); //Align the entity on the plane properly // pEntity->AlignOnPlane(HitPos, &pFace->plane, (pFace->plane.normal[2] > 0.0f) ? CMapEntity::ALIGN_BOTTOM : CMapEntity::ALIGN_TOP); pEntity->AlignOnPlane(HitPos, &pFace->plane, (HitNormal[2] > 0.0f) ? CMapEntity::ALIGN_BOTTOM : CMapEntity::ALIGN_TOP); CMapWorld *pWorld = pDoc->GetMapWorld(); pDoc->AddObjectToWorld(pEntity); GetHistory()->KeepNew(pEntity); // // Select the new object. // pDoc->SelectObject(pEntity, CMapDoc::scClear | CMapDoc::scSelect | CMapDoc::scUpdateDisplay); UpdateBox ub; CMapObjectList ObjectList; ObjectList.AddTail(pEntity); ub.Objects = &ObjectList; Vector mins; Vector maxs; pEntity->GetRender2DBox(mins, maxs); ub.Box.SetBounds(mins, maxs); pDoc->UpdateAllViews(NULL, MAPVIEW_UPDATE_OBJECTS, &ub); pDoc->SetModifiedFlag(); } } } return true; }