diff --git a/Mammoth/Include/TSETopology.h b/Mammoth/Include/TSETopology.h index 570e1487e..2472913b6 100644 --- a/Mammoth/Include/TSETopology.h +++ b/Mammoth/Include/TSETopology.h @@ -106,6 +106,7 @@ class CTopologyNode CTopologyNode *GetStargateDest (int iIndex, CString *retsEntryPoint = NULL) const; ICCItemPtr GetStargateProperty (const CString &sName, const CString &sProperty) const; void GetStargateRouteDesc (int iIndex, SStargateRouteDesc *retRouteDesc) const; + CString GetStargateName (int iIndex) const { return m_NamedGates.GetKey(iIndex); } CSystem *GetSystem (void) { return m_pSystem; } DWORD GetSystemID (void) { return m_dwID; } const CString &GetSystemName (void) const { return m_sName; } @@ -127,6 +128,7 @@ class CTopologyNode bool IsMarked (void) const { return m_bMarked; } bool IsNull (void) const { return (m_SystemUNID == 0 || IsEndGame()); } bool IsPositionKnown (void) const { return (m_bKnown || m_bPosKnown); } + bool MatchesStargateAttribs (const CString &sName, const CString &sMatch) const; void SetCalcDistance (int iDist) const { m_iCalcDistance = iDist; } void SetCreatorID (const CString &sID) { m_sCreatorID = sID; } void SetData (const CString &sAttrib, ICCItem *pData) { m_Data.SetData(sAttrib, pData); } @@ -189,6 +191,8 @@ class CTopologyNode mutable CTopologyNode *pDestNode = NULL; // Cached for efficiency (may be NULL) }; + bool MatchesStargateAttribs (const CTopologyNode::SStargateEntry* pGate, const CString &sMatch) const; + CTopology &m_Topology; // Topology that we're a part of CString m_sID; // ID of node CString m_sCreatorID; // ID of topology desc, if created by a fragment, etc. @@ -385,6 +389,7 @@ class CTopology { public: static const int UNKNOWN_DISTANCE = -1; + static const int BLOCKED_DISTANCE = -2; struct SNodeCreateCtx { @@ -427,11 +432,46 @@ class CTopology const CTopologyNode *FindTopologyNode (const CString &sID) const; CTopologyNode *FindTopologyNode (const CString &sID); CString GenerateUniquePrefix (const CString &sPrefix, const CString &sTestNodeID); - int GetDistance (const CTopologyNode *pSrc, const CTopologyNode *pTarget) const; - int GetDistance (const CString &sSourceID, const CString &sDestID) const; + int GetDistance ( + const CTopologyNode *pSrc, + const CTopologyNode *pTarget, + const CString &sGateCriteria = NULL_STR, + const TArray &aUseNodes = TArray(), + const TArray &aBlockNodes = TArray(), + bool bIgnoreOneWay = true, + bool bAllowUseNodeBacktrack = true) const; + int GetDistance ( + const CString &sSourceID, + const CString &sDestID, + const CString &sGateCriteria = NULL_STR, + const TArray &aUseNodes = TArray(), + const TArray &aBlockNodes = TArray(), + bool bIgnoreOneWay = true, + bool bAllowUseNodeBacktrack = true) const; int GetDistanceToCriteria (const CTopologyNode *pSrc, const CTopologyAttributeCriteria &Criteria) const; int GetDistanceToCriteriaNoMatch (const CTopologyNode *pSrc, const CTopologyAttributeCriteria &Criteria) const; - const CTopologyNode *GetNextNodeTo (const CTopologyNode &From, const CTopologyNode &To) const; + const CTopologyNode *GetNextNodeTo ( + const CTopologyNode &From, + const CTopologyNode &To, + const CString &sGateCriteria = NULL_STR, + const TArray &aUseNodes = TArray(), + const TArray &aBlockNodes = TArray(), + bool bIgnoreOneWay = true, + bool bAllowUseNodeBacktrack = true) const; + const TArray GetPathTo ( + const CTopologyNode *pSrc, + const CTopologyNode *pTarget, + const CString &sGateCriteria = NULL_STR, + const TArray &aUseNodes = TArray(), + const TArray &aBlockNodes = TArray(), + bool bIgnoreOneWay = true, + bool bAllowUseNodeBacktrack = true) const; + const TArray GetPathTo ( + const CTopologyNode *pSrc, + const CTopologyNode *pTarget, + const CString &sGateCriteria = NULL_STR, + const TArray &aBlockNodes = TArray(), + bool bIgnoreOneWay = true) const; CTopologyNodeList &GetTopologyNodeList (void) { return m_Topology; } CTopologyNode *GetTopologyNode (int iIndex) { return &m_Topology[iIndex]; } const CTopologyNode *GetTopologyNode (int iIndex) const { return &m_Topology[iIndex]; } diff --git a/Mammoth/Include/TSEVersions.h b/Mammoth/Include/TSEVersions.h index 337f2122d..a1556f27a 100644 --- a/Mammoth/Include/TSEVersions.h +++ b/Mammoth/Include/TSEVersions.h @@ -682,6 +682,19 @@ constexpr DWORD SYSTEM_SAVE_VERSION = 215; // Returns a list of static datakeys for the given obj type // (scr@Keys type) // Returns a list of all instance property and custom global property keys for the given obj +// (sysGetNextNodeTo [srcNode] destNode [options]) +// Accepts an options struct now +// options: +// blockNodes (list): do not path through these nodes +// respectOneWayGates (bool): if pathing must obey the directionality of one way gates (default: false) +// gateCriteria (string): criteria to match against stargate topology attributes (not the stations) +// useNodes (list): these nodes must be pathed through +// (sysGetPathTo [srcNode] destNode [options]) +// options: +// blockNodes (list): do not path through these nodes +// respectOneWayGates (bool): if pathing must obey the directionality of one way gates (default: false) +// gateCriteria (string): criteria to match against stargate topology attributes (not the stations) +// useNodes (list): these nodes must be pathed through // // // damage: (str: damage desc) diff --git a/Mammoth/TSE/CCExtensions.cpp b/Mammoth/TSE/CCExtensions.cpp index 10b19b097..8592d9ce1 100644 --- a/Mammoth/TSE/CCExtensions.cpp +++ b/Mammoth/TSE/CCExtensions.cpp @@ -518,6 +518,7 @@ ICCItem *fnSystemAddStationTimerEvent (CEvalContext *pEvalCtx, ICCItem *pArgs, D #define FN_SYS_ADD_STARGATE_TOPOLOGY_COLORED 44 #define FN_SYS_STARGATE_HAS_ATTRIBUTE 45 #define FN_SYS_GET_DATA_KEYS 46 +#define FN_SYS_PATH_TO 47 #define OPT_SYS_ADD_STARGATE_TOPOLOGY_COLOR CONSTLIT("color") #define OPT_SYS_ADD_STARGATE_ATTRIBUTES CONSTLIT("attributes") @@ -3377,8 +3378,16 @@ static PRIMITIVEPROCDEF g_Extensions[] = "iiii", 0, }, { "sysGetNextNodeTo", fnSystemGet, FN_SYS_NEXT_NODE_TO, - "(sysGetNextNodeTo [fromNodeID] toNodeID) -> nodeID", - "*s", 0, }, + "(sysGetNextNodeTo [fromNodeID] toNodeID [options]) -> nodeID\n\n" + + "options (struct):\n\n" + + " respectOneWayGates: Respects directionality of one-way gates when pathing through them. Does not respect one-way gates by default.\n" + " gateCriteria: Only gates that match criteria can be used for the path calculations\n" + " useNodes: A list of nodes that must be included in the path calculations\n" + " blockNodes: A list of nodes that cannot be included in the path calculations\n", + + "*", 0, }, { "sysGetNode", fnSystemGet, FN_SYS_NODE, "(sysGetNode) -> nodeID", @@ -3400,6 +3409,18 @@ static PRIMITIVEPROCDEF g_Extensions[] = "(sysGetObjectByName [source] name) -> obj", "*s", 0, }, + { "sysGetPathTo", fnSystemGet, FN_SYS_PATH_TO, + "(sysGetPathTo [fromNodeID] toNodeID [options]) -> nodeID\n\n" + + "options (struct):\n\n" + + " respectOneWayGates: Respects directionality of one-way gates when pathing through them. Does not respect one-way gates by default.\n" + " gateCriteria: Only gates that match criteria can be used for the path calculations\n" + " useNodes: A list of nodes that must be included in the path calculations\n" + " blockNodes: A list of nodes that cannot be included in the path calculations\n", + + "*", 0, }, + { "sys@", fnSystemGet, FN_SYS_GET_PROPERTY, "(sys@ [nodeID] property) -> value\n\n" @@ -14559,20 +14580,26 @@ ICCItem *fnSystemGet (CEvalContext *pEvalCtx, ICCItem *pArgs, DWORD dwData) return pCC->CreateInteger(iFreq); } + case FN_SYS_PATH_TO: case FN_SYS_NEXT_NODE_TO: { int iArg = 0; - // If we have more than 1 args, then the first arg is the fromID + // If we have 2 args and the last is not the option structs, + // or we have 3 args, then the first arg is the fromID const CTopologyNode *pFromNode; - if (pArgs->GetCount() > 1 && pArgs->GetElement(0)->IsIdentifier()) + if ((pArgs->GetCount() == 2 && pArgs->GetElement(0)->IsIdentifier() && !pArgs->GetElement(1)->IsSymbolTable()) + || pArgs->GetCount() == 3) { pFromNode = pCtx->GetUniverse().FindTopologyNode(pArgs->GetElement(iArg++)->GetStringValue()); if (pFromNode == NULL) return pCC->CreateError(CONSTLIT("Invalid nodeID"), pArgs->GetElement(0)); } + else if (pArgs->GetCount() > 3) + return pCC->CreateError(CONSTLIT("Too many args"), pArgs); + // Otherwise, we assume the current system. else @@ -14592,13 +14619,96 @@ ICCItem *fnSystemGet (CEvalContext *pEvalCtx, ICCItem *pArgs, DWORD dwData) if (pToNode == NULL) return pCC->CreateError(CONSTLIT("Invalid nodeID"), pArgs->GetElement(iArg - 1)); + // Check if there is an options arg + + ICCItem *pOptions = pArgs->GetElement(iArg++); + + TArray aUseNodes; + TArray aBlockNodes; + CString sGateCriteria; + bool bRespectOneWayGates; + + if (pOptions) + { + // We only accept a struct for options + + if (pOptions->IsSymbolTable()) + { + // By default we do not respect one way gates (this is legacy behavior) + + bRespectOneWayGates = pOptions->GetBooleanAt(CONSTLIT("respectOneWayGates")); + + // Empty gate criteria is ignored + + sGateCriteria = pOptions->GetStringAt(CONSTLIT("gateCriteria")); + + // list of node IDs to use + + ICCItem* pUseNodes = pOptions->GetElement(CONSTLIT("useNodes")); + if (pUseNodes) + { + if (pUseNodes->IsList()) + { + aUseNodes.InsertEmpty(pUseNodes->GetCount()); + + for (int i = 0; i < pUseNodes->GetCount(); i++) + aUseNodes[i] = pUseNodes->GetElement(i)->GetStringValue(); + } + + // otherwise we assume its just one node name + + else + aUseNodes.Insert(pUseNodes->GetStringValue()); + } + + // list of node IDs to avoid + + ICCItem* pBlockNodes = pOptions->GetElement(CONSTLIT("blockNodes")); + if (pBlockNodes) + { + if (pBlockNodes->IsList()) + { + aBlockNodes.InsertEmpty(pBlockNodes->GetCount()); + + for (int i = 0; i < pBlockNodes->GetCount(); i++) + aBlockNodes[i] = pBlockNodes->GetElement(i)->GetStringValue(); + } + + // otherwise we assume its just one node name + + else + aBlockNodes.Insert(pBlockNodes->GetStringValue()); + } + } + + // Otherwise this is invalid + + else + return pCC->CreateError(CONSTLIT("Invalid options, must be a struct"), pOptions); + } + // Compute - const CTopologyNode *pNextNode = pCtx->GetUniverse().GetTopology().GetNextNodeTo(*pFromNode, *pToNode); - if (!pNextNode) - return pCC->CreateNil(); + if (dwData == FN_SYS_NEXT_NODE_TO) + { + const CTopologyNode *pNextNode = pCtx->GetUniverse().GetTopology().GetNextNodeTo(*pFromNode, *pToNode, sGateCriteria, aUseNodes, aBlockNodes, !bRespectOneWayGates); + if (!pNextNode) + return pCC->CreateNil(); - return pCC->CreateString(pNextNode->GetID()); + return pCC->CreateString(pNextNode->GetID()); + } + else + { + ICCItem *pList = pCC->CreateLinkedList(); + + TArray aPath; + aPath = pCtx->GetUniverse().GetTopology().GetPathTo(pFromNode, pToNode, sGateCriteria, aUseNodes, aBlockNodes, !bRespectOneWayGates); + + for (int i = 0; i < aPath.GetCount(); i++) + pList->AppendString(aPath[i]->GetID()); + + return pList; + } } case FN_SYS_LOCATIONS: diff --git a/Mammoth/TSE/CTopology.cpp b/Mammoth/TSE/CTopology.cpp index 8354af1c9..e0625cb2b 100644 --- a/Mammoth/TSE/CTopology.cpp +++ b/Mammoth/TSE/CTopology.cpp @@ -1424,12 +1424,19 @@ CString CTopology::GenerateUniquePrefix (const CString &sPrefix, const CString & return sTestPrefix; } -int CTopology::GetDistance (const CString &sSourceID, const CString &sDestID) const - // GetDistance // // Returns the shortest distance between the two nodes. If there is no path between // the two nodes, we return UNKNOWN_DISTANCE (-1). +// +int CTopology::GetDistance ( + const CString &sSourceID, + const CString &sDestID, + const CString &sGateCriteria, + const TArray &aUseNodes, + const TArray &aBlockNodes, + bool bIgnoreOneWay, + bool bAllowUseNodeBacktrack) const { // Find the source node in the list @@ -1444,81 +1451,33 @@ int CTopology::GetDistance (const CString &sSourceID, const CString &sDestID) co if (pDest == NULL) return UNKNOWN_DISTANCE; - return GetDistance(pSource, pDest); + return GetDistance(pSource, pDest, sGateCriteria, aUseNodes, aBlockNodes, bIgnoreOneWay); } -int CTopology::GetDistance (const CTopologyNode *pSrc, const CTopologyNode *pTarget) const - // GetDistance // // Returns the shortest distance between the two nodes. If there is no path between // the two nodes, we return UNKNOWN_DISTANCE (-1). +// +int CTopology::GetDistance ( + const CTopologyNode *pSrc, + const CTopologyNode *pTarget, + const CString &sGateCriteria, + const TArray &aUseNodes, + const TArray &aBlockNodes, + bool bIgnoreOneWay, + bool bAllowUseNodeBacktrack) const { - if (GetTopologyNodeCount() < 2 || pSrc == NULL || pTarget == NULL) - return UNKNOWN_DISTANCE; - else if (pSrc == pTarget) - return 0; - - // We start by marking all nodes with UNKNOWN_DISTANCE. This helps us keep - // track of nodes that we still need to compute. - - for (int i = 0; i < GetTopologyNodeCount(); i++) - GetTopologyNode(i)->SetCalcDistance(UNKNOWN_DISTANCE); - - // Now we set the source node to distance 0 - - pSrc->SetCalcDistance(0); - - // We expand out from the known nodes to unknown nodes - int iDistance = 1; - TArray Worklist; - Worklist.Insert(pSrc); - while (Worklist.GetCount() > 0) - { - TArray Found; + // Because GetPathTo always includes pSrc as the first node, we just need to subtract one from + // the length. + // Although currently UNKNOWN_DISTANCE is -1, we do an explicit check for an empty array + // so that we can return UNKNOWN_DISTANCE explicitly in case that value is ever changed. - // Loop over all node in the worklist and find nodes that are - // connected. + int iPathLength = GetPathTo(pSrc, pTarget, sGateCriteria, aUseNodes, aBlockNodes, bIgnoreOneWay).GetCount() - 1; - for (int i = 0; i < Worklist.GetCount(); i++) - { - const CTopologyNode *pNode = Worklist[i]; - - for (int j = 0; j < pNode->GetStargateCount(); j++) - { - const CTopologyNode *pDest = pNode->GetStargateDest(j); - if (pDest == NULL) - continue; - - // If we found the destination node, then we figured out the - // distance. - - if (pDest == pTarget) - return iDistance; - - // If this node has not yet been found, then we set the - // distance, since we can guarantee that it is 1 hop away from - // a known node (a node in the Worklist). - - else if (pDest->GetCalcDistance() == UNKNOWN_DISTANCE) - { - pDest->SetCalcDistance(iDistance); - Found.Insert(pDest); - } - } - } - - // Now we deal with the nodes that we found. - - Worklist = Found; - iDistance++; - } - - // If we ran out of nodes it means we could not find a path. - - return UNKNOWN_DISTANCE; + return iPathLength ? iPathLength - 1 : UNKNOWN_DISTANCE; } int CTopology::GetDistanceToCriteria (const CTopologyNode *pSrc, const CTopologyAttributeCriteria &Criteria) const @@ -1591,7 +1550,14 @@ int CTopology::GetDistanceToCriteriaNoMatch (const CTopologyNode *pSrc, const CT return iBestDist; } -const CTopologyNode *CTopology::GetNextNodeTo (const CTopologyNode &From, const CTopologyNode &To) const +const CTopologyNode *CTopology::GetNextNodeTo ( + const CTopologyNode &From, + const CTopologyNode &To, + const CString &sGateCriteria, + const TArray &aUseNodes, + const TArray &aBlockNodes, + bool bIgnoreOneWay, + bool bAllowUseNodeBacktrack) const // GetNextNodeTo // @@ -1602,7 +1568,7 @@ const CTopologyNode *CTopology::GetNextNodeTo (const CTopologyNode &From, const // Compute distance in reverse direction (To -> From). This will initialize // the m_iCalcDistance on each node with a distance. - if (GetDistance(&To, &From) == UNKNOWN_DISTANCE) + if (GetDistance(&To, &From, sGateCriteria, aUseNodes, aBlockNodes, bIgnoreOneWay) == UNKNOWN_DISTANCE) return NULL; // Now loop over all nodes adjacent to From and pick the shortest path. @@ -1621,6 +1587,547 @@ const CTopologyNode *CTopology::GetNextNodeTo (const CTopologyNode &From, const return pBestNode; } +// GetPathTo +// +// Return an array of node pointers including nodes From to To. If +// there is no path, we return an empty TArray. +// +const TArray CTopology::GetPathTo( + const CTopologyNode* pSrc, + const CTopologyNode* pTarget, + const CString& sGateCriteria, + const TArray& aUseNodes, + const TArray& aBlockNodes, + bool bIgnoreOneWay, + bool bAllowUseNodeBacktrack) const + { + // We handle aUseNodes separately to dramatically simplify the rest of the GetPathTo logic + // + // If we dont even have aUseNodes, just passthrough and skip the rest of this function + + if (!aUseNodes.GetCount()) + return GetPathTo(pSrc, pTarget, sGateCriteria, aBlockNodes, bIgnoreOneWay); + + // Otherwise we need to do special handling for all of the useNodes to ensure we hit them + // + // Its generally not expected that this is going to be performance-sensitive code, so we + // dont need to have a super-optimal solution, but its still dramatically more complex + // than skipping nodes or gates + + // Check if its even possible to reach pTarget from pSrc to save some time + + TArray aRet = GetPathTo(pSrc, pTarget, sGateCriteria, aBlockNodes, bIgnoreOneWay); + + if (!aRet.GetCount()) + return aRet; + + aRet.DeleteAll(); + + TArray aNodes; + + // These numbers include the src and dest nodes + + int iNumSequences = aUseNodes.GetCount() + 1; + int iNumNodes = aUseNodes.GetCount() + 2; + + aNodes.InsertEmpty(iNumNodes); + aNodes[0] = pSrc; + + for (int i = 0; i < aUseNodes.GetCount(); i++) + { + const CTopologyNode* pUsedNode = FindTopologyNode(aUseNodes[i]); + + // If a required node doesnt exist, this path cannot possibly be completed + + if (!pUsedNode) + return aRet; + + aNodes[i + 1] = pUsedNode; + } + aNodes[aUseNodes.GetCount() + 1] = pTarget; + + const CTopologyNode* pCurSrc = pSrc; + const CTopologyNode* pCurDest = NULL; + + if (bAllowUseNodeBacktrack) + { + // unfortunately due to needing to be able to wrap around we do need to + // consider all O(n^2) rows because a gate may be blocked on one side by + // either criteria or being oneWay + // + // The last node cant be pathed to anything else so we can skip that too + + struct SPath + { + const CTopologyNode* pSrc = NULL; + const CTopologyNode* pDest = NULL; + TArray aPath; + }; + TArray> aAllPaths; + + aAllPaths.InsertEmpty(iNumSequences); + for (int i = 0; i < aNodes.GetCount() - 1; i++) + { + int iRowStart = i + 1; + TArray aRow; + aRow.InsertEmpty(aNodes.GetCount() - iRowStart); + + for (int k = iRowStart; k < aNodes.GetCount(); k++) + { + const CTopologyNode* pLocalSrc = aNodes[i]; + const CTopologyNode* pLocalDest = aNodes[k]; + int iK = k - iRowStart; + aRow[iK].pSrc = pLocalSrc; + aRow[iK].pDest = pLocalDest; + aRow[iK].aPath = GetPathTo(pLocalSrc, pLocalDest, sGateCriteria, aBlockNodes, bIgnoreOneWay); + } + + aAllPaths[i] = aRow; + } + + // Now that we have all the paths, we find the optimal one that satisfies the criteria + + TArray aBestSequence; + aBestSequence.InsertEmpty(iNumSequences); + TArray aCurSequence; + aCurSequence.InsertEmpty(iNumSequences); + TArray aRowIdx; + aRowIdx.InsertEmpty(iNumSequences); + + // For some reason InsertEmpty isnt actually zeroing it out + // or InsertEmpty is misleadingly named + + for (int i = 0; i < aRowIdx.GetCount(); i++) + aRowIdx[i] = 0; + + int iLastCheckedRow = 0; + int iBestPathLength = 0; + + bool bProcessing = true; + bool bSkip = false; + + while (bProcessing) + { + // create the sequence for the current row indexes + + int iCurPathLength = 0; + for (int i = 0; i < aRowIdx.GetCount(); i++) + { + int r = aRowIdx[i]; + SPath sCurPath = aAllPaths[i][r]; + aCurSequence[i] = sCurPath; + iCurPathLength += sCurPath.aPath.GetCount(); + + // short circuit if the path is already too long + + if (iBestPathLength && iCurPathLength > iBestPathLength) + { + bSkip = true; + iLastCheckedRow = i; + break; + } + } + + // copy the current sequence over if it is best + + if (iCurPathLength < iBestPathLength || !iBestPathLength) + { + for (int i = 0; i < aCurSequence.GetCount(); i++) + aBestSequence[i] = aCurSequence[i]; + } + + // check the next permutation + // + // If we skip, we know that this point in the permutation sequence + // is already bad so dont even bother checking any others past it + // + // We also check if we are still processing + + bProcessing = false; + + for (int i = aRowIdx.GetCount() - 2; i >= 0; i--) + { + // check if we have room to increment this row + + if (aRowIdx[i] < aAllPaths[i].GetCount() - 1) + { + // We are still processing, even if we don't bump this row + + bProcessing = true; + + // If we are skipping and too low, we just jump ahead to edit this row + + if (bSkip && i < iLastCheckedRow) + i = iLastCheckedRow + 1; + + // Bump the index on this row and exit out + + aRowIdx[i]++; + break; + } + + // otherwise we try to reset this row and bump the next one + aRowIdx[i] = 0; + } + } + + // now we populate aRet + + aRet.Insert(pSrc); + for (int i = 0; i < aBestSequence.GetCount(); i++) + { + // we skip the first element of each path since we do it in the previous + // iteration + + for (int k = 0; k < aBestSequence[i].aPath.GetCount(); k++) + aRet.Insert(aBestSequence[i].aPath[k]); + } + + return aRet; + } + else + { + // Not supporting this yet + return aRet; + + // if we can't backtrack, the permutations become truly cursed + // we go depth-first in this case since it lets us start culling bad results early. + struct SPath + { + const CTopologyNode* pSrc = NULL; + const CTopologyNode* pDest = NULL; + SPath* pParent = NULL; + TArray aPath; + TArray aBlocked; + TMap mBranches; + }; + + TArray aPaths; + aPaths.InsertEmpty(aNodes.GetCount() - 1); + TArray aRowIdx; + aRowIdx.InsertEmpty(aPaths.GetCount()); + int iDepth = 0; + SPath *pCurPath; + TArray aUsedStack; + TMap mUsed; + TArray aRemaining; + + aRemaining.InsertEmpty(aNodes.GetCount() - 1); + for (int i = 0; i < aRemaining.GetCount(); i++) + aRemaining[i] = aNodes[i]; + + for (int i = 0; i < aNodes.GetCount() - 1; i++) + { + pCurPath = &aPaths[i]; + while (iDepth < aNodes.GetCount() - 1) + { + // first we need to see what + + //TArray aAttemptedPath = GetPathTo(aRowIdx[k].pSrc, aRow[k].pDest, sGateCriteria, aBlockNodes, bIgnoreOneWay); + } + + + + } + } + } + +// GetPathTo +// +// Return an array of node pointers including nodes From to To. If +// there is no path, we return an empty TArray. +// +const TArray CTopology::GetPathTo( + const CTopologyNode *pSrc, + const CTopologyNode *pTarget, + const CString& sGateCriteria, + const TArray& aBlockNodes, + bool bIgnoreOneWay) const + { + TArray aRet; + + if (pSrc == NULL || pTarget == NULL) + return aRet; + else if (pSrc == pTarget) + { + aRet.Insert(pSrc); + return aRet; + } + + TMap mBlock; + + // Note that unlike required nodes, blocked nodes that do not exist do not + // matter. We are only comparing pointers anyways, so NULLs are safe here. + + for (int i = 0; i < aBlockNodes.GetCount(); i++) + mBlock.Insert(FindTopologyNode(aBlockNodes[i])); + + // Ensure that our source and target are not blocked, if they are, no path + + if (mBlock.Find(pSrc) || mBlock.Find(pTarget)) + return aRet; + + // We start by marking all nodes with UNKNOWN_DISTANCE. This helps us keep + // track of nodes that we still need to compute. + + for (int i = 0; i < GetTopologyNodeCount(); i++) + GetTopologyNode(i)->SetCalcDistance(UNKNOWN_DISTANCE); + + // Now we set the source node to distance 0 + + pSrc->SetCalcDistance(0); + + // We will need to remember what invalid paths we had due to those paths + // not containing all of the required nodes + + TArray> aInvalidPaths; + + // We expand out from the known nodes to unknown nodes + + int iDistance = 1; + TArray Worklist; + TMap> mBlockedConnections; + Worklist.Insert(pSrc); + while (Worklist.GetCount() > 0) + { + TArray Found; + + // Loop over all node in the worklist and find nodes that are + // connected. + + for (int i = 0; i < Worklist.GetCount(); i++) + { + const CTopologyNode *pNode = Worklist[i]; + + for (int j = 0; j < pNode->GetStargateCount(); j++) + { + const CTopologyNode *pDest = pNode->GetStargateDest(j); + + // We skip any non-system + + if (pDest == NULL) + continue; + + // We mark systems we cannot traverse as blocked + + if (mBlock.Find(pDest)) + { + pDest->SetCalcDistance(BLOCKED_DISTANCE); + continue; + } + + // Additionally we skip pDest if the gate to pDest is disallowed either by + // criteria or because we require unidirectional gates to be entered from + // the accessible side + + CTopologyNode::SStargateRouteDesc Gate; + pNode->GetStargateRouteDesc(j, &Gate); + + // Are we the from or to side? + + bool bTo = pNode == Gate.pToNode; + + // If we are the to side, and this is a one way gate, we cannot go back through + // unless we ignore one way gates (legacy behavior, so it is the default) + // + // One way gates only allow from -> to + // + // Note that instead of setting the distance on the other node to blocked, + // as a different valid path may pass through it using other gates, + // we instead leave it as unknown (or whatever the prior value was) + // and store this connection as being blocked + + if (!bIgnoreOneWay && Gate.bOneWay && bTo) + { + TMap *pSet = mBlockedConnections.Find(pDest); + + if (pSet) + pSet->Insert(pNode); + else + { + TMap mSet; + mBlockedConnections.Insert(pDest, mSet); + mSet.Insert(pNode); + } + + continue; + } + + // Now we need to check the gate criteria. Criteria must match on both sides. + // Blank criteria always matches so we can shortcircuit it + // + // As with one way gates we dont inherently mark the node itself as blocked + + if (!sGateCriteria.IsBlank()) + { + CString sSrcGateName = pNode->GetStargateName(j); + CString sDestGateName = bTo ? Gate.sFromName : Gate.sToName; + bool bBlocked = false; + + if (!pNode->MatchesStargateAttribs(sSrcGateName, sGateCriteria)) + { + bBlocked = true; + + TMap *pSet = mBlockedConnections.Find(pNode); + + if (pSet) + pSet->Insert(pDest); + else + { + TMap mSet; + mBlockedConnections.Insert(pNode, mSet); + mSet.Insert(pDest); + } + } + + if (!pDest->MatchesStargateAttribs(sDestGateName, sGateCriteria)) + { + bBlocked = true; + + TMap *pSet = mBlockedConnections.Find(pDest); + + if (pSet) + pSet->Insert(pNode); + else + { + TMap mSet; + mBlockedConnections.Insert(pDest, mSet); + mSet.Insert(pNode); + } + } + + if (bBlocked) + continue; + } + + // If we found the destination node, we must now check if this + // is a valid path. + + if (pDest == pTarget) + { + // We backtrack from the highest to lowest distance nodes + // + // We know that the next node in a valid path is distance-1 + // + // We skip any seeming 'shortcuts' because those are caused by + // Impassible gates (we still need to validate, because a + // set of parallel systems forming a 'ladder' topology where + // some of the adjacent systems may seem to be valid connections + // by this rule alone, but the gate connecting them may still + // be prohibited + + TArray aRetReverse; + + // We need to iterate backwards to check this path + + aRetReverse.Insert(pDest); + + const CTopologyNode* pReverseNode = pNode; + int iRemainingDistance = iDistance - 1; + const CTopologyNode* pNextReverseNode = NULL; + + while (pReverseNode != pSrc) + { + aRetReverse.Insert(pReverseNode); + + // Check what gates we can use + // + // We also check if we are at a fork in the road + // + // This is when 2+ next distances are all valid + // We keep these in mind to backtrack to + + for (int k = 0; k < pReverseNode->GetStargateCount(); k++) + { + const CTopologyNode* pPriorNode = pReverseNode->GetStargateDest(k); + if (!pPriorNode) + continue; + + int iPriorDistance = pPriorNode->GetCalcDistance(); + + if (iPriorDistance == UNKNOWN_DISTANCE || iPriorDistance == BLOCKED_DISTANCE) + continue; + + // If the prior distance is not exactly less than one, its definitely + // a false-shortcut due to a disallowed gate (Ex, one-way) so dont even bother + // checking it + + if (iPriorDistance == (iRemainingDistance - 1)) + { + // We still need to validate that this gate is an allowed connection + // + // The connection from pPriorNode (the next prior one) to + // pReverseNode (the one we are looking at) must be open + + TMap *pSet = mBlockedConnections.Find(pPriorNode); + + // If pPriorNode has any outgoing blockages we want to ensure one of them isnt us + + if (pSet && pSet->Find(pReverseNode)) + continue; + + // In this case everything looks good - pick this gate + + pNextReverseNode = pReverseNode->GetStargateDest(k); + break; + } + } + + // We should always have found a valid path back + // This debug assert is here to catch cases where that doesnt happen (ex, due to code changes) + + ASSERT(pNextReverseNode); + + // Prepare for the next iteration... + + pReverseNode = pNextReverseNode; + iRemainingDistance--; + } + + // We just to add the source node to complete our path. + + aRetReverse.Insert(pSrc); + + // We have our path! + // We just need to flip it around in the correct order. + + int iLen = aRetReverse.GetCount(); + aRet.InsertEmpty(iLen); + + for (int k = 0; k < iLen; k++) + { + int r = iLen - 1 - k; + aRet[k] = aRetReverse[r]; + } + + return aRet; + } + + // If this node has not yet been found, then we set the + // distance, since we can guarantee that it is 1 hop away from + // a known node (a node in the Worklist). + // + // we make sure that it is not a blocked node + + else if (pDest->GetCalcDistance() == UNKNOWN_DISTANCE) + { + pDest->SetCalcDistance(iDistance); + Found.Insert(pDest); + } + } + } + + // Now we deal with the nodes that we found. + + Worklist = Found; + iDistance++; + } + + // If we ran out of nodes it means we could not find a path. + + return aRet; + } + ALERROR CTopology::GetOrAddTopologyNode (STopologyCreateCtx &Ctx, const CString &sID, CTopologyNode **retpNode) diff --git a/Mammoth/TSE/CTopologyNode.cpp b/Mammoth/TSE/CTopologyNode.cpp index c79356175..a80a2ae56 100644 --- a/Mammoth/TSE/CTopologyNode.cpp +++ b/Mammoth/TSE/CTopologyNode.cpp @@ -726,11 +726,11 @@ ICCItemPtr CTopologyNode::GetStargateProperty (const CString &sName, const CStri return ICCItemPtr(ICCItem::Nil); } -void CTopologyNode::GetStargateRouteDesc (int iIndex, SStargateRouteDesc *retRouteDesc) const - // GetStargateRouteDesc // // Returns route description +// +void CTopologyNode::GetStargateRouteDesc (int iIndex, SStargateRouteDesc *retRouteDesc) const { const SStargateEntry *pDesc = &m_NamedGates[iIndex]; @@ -869,6 +869,100 @@ ALERROR CTopologyNode::InitFromSystemXML (CTopology &Topology, CXMLElement *pSys return NOERROR; } +// MatchesStargateAttribs +// +// Checks if a stargate's link attributes match the pattern in the match string +// This is done criteria-style, but only checks against attributes +// +bool CTopologyNode::MatchesStargateAttribs(const CString& sName, const CString& sMatch) const + { + CTopologyNode::SStargateEntry Gate; + + // An invalid gate never matches + if (!m_NamedGates.Find(sName, &Gate)) + return false; + + // Empty match string always matches a valid gate + if (sMatch.IsBlank()) + return true; + + return MatchesStargateAttribs(&Gate, sMatch); + } + +// MatchesStargateAttribs +// +// Checks if a stargate's link attributes match the pattern in the match string +// This is done criteria-style, but only checks against attributes +// +bool CTopologyNode::MatchesStargateAttribs(const CTopologyNode::SStargateEntry *pGate, const CString& sMatch) const + { + // If a single non-or criteria fails, we do not match unless + // we succeed on any or-criteria + + bool bMatches = true; + + // iterate through the string looking for + or - attributes + + char* pPos = sMatch.GetPointer(); + char* pInitialPos = pPos; + + // cease if we hit a null terminator or OR criteria + + while (*pPos && *pPos != '|') + { + // if we are in whitespace, skip ahead + + while (strIsWhitespace(pPos)) + pPos++; + + // we have reached something now + // if it is a null terminator or OR criteria, just leave + + if (!*pPos || *pPos == '|') + break; + + // otherwise we check if this is a '-' for a negative criteria + + bool bIsNegative = *pPos == '-'; + + // skip ahead by one if this is a '+' or '-' to the actual attribute + + if (*pPos == '-' || *pPos == '+') + pPos++; + + char *pAttribStart = pPos; + + // scan ahead until we hit a delimiter + while (*pPos && *pPos != '|' && *pPos != ',' && *pPos != ';' && !strIsWhitespace(pPos)) + pPos++; + + CString sSelectedAttrib = strSubString(sMatch, pAttribStart - pInitialPos, pPos - pAttribStart); + + // determine if we satisfy the criteria + + bool bMatchedAttrib = HasModifier(pGate->sAttributes, sSelectedAttrib); + + bMatches = bMatchedAttrib != bIsNegative; + + // if we failed, stop evaluating + + if (!bMatches) + break; + } + + // If we failed to match, we try the next or condition if one exists + + if (!bMatches && *pPos == '|') + { + // Always start 1 char after the '|' criteria + CString sOrMatch = strSubString(sMatch, strFind(sMatch, CONSTLIT("|")) + 1); + if (!sOrMatch.IsBlank()) + bMatches = MatchesStargateAttribs(pGate, sOrMatch); + } + + return bMatches; + } + ALERROR CTopologyNode::ParsePointList (const CString &sValue, TArray *retPoints) // ParsePointList