#include "common.h" #include "patcher.h" #include "PathFind.h" CPathFind &ThePaths = *(CPathFind*)0x8F6754; int TempListLength; enum { NodeTypeExtern = 1, NodeTypeIntern = 2, PathTypeCar = 0, PathTypePed = 1, PathNodeFlag1 = 1, // used? PathNodeFlag2 = 2, PathNodeDeadEnd = 4, PathNodeDisabled = 8, PathNodeBetweenLevels = 0x10, ConnectionCrossRoad = 1, ConnectionTrafficLight = 2, }; // link flags: // 1: crosses road // 2: ped traffic light // pathnode flags: // 1: // 2: // 4: dead end // 8: switched off // 10: road between levels?? // navi node flags: // 1: bridge light // object flags: // 1 // 2 east/west road(?) CPathInfoForObject *&InfoForTileCars = *(CPathInfoForObject**)0x8F1A8C; CPathInfoForObject *&InfoForTilePeds = *(CPathInfoForObject**)0x8F1AE4; // unused CTempDetachedNode *&DetachedNodesCars = *(CTempDetachedNode**)0x8E2824; CTempDetachedNode *&DetachedNodesPeds = *(CTempDetachedNode**)0x8E28A0; void CPathFind::PreparePathData(void) { int i, j, k; int numExtern, numIntern, numLanes; float maxX, maxY; CTempNode *tempNodes; printf("PreparePathData\n"); // UNUSED: CPathFind::LoadPathFindData if(InfoForTileCars && InfoForTilePeds && DetachedNodesCars && DetachedNodesPeds){ tempNodes = new CTempNode[4000]; m_numConnections = 0; for(i = 0; i < PATHNODESIZE; i++) m_pathNodes[i].flags &= ~(PathNodeFlag1 | PathNodeFlag2); for(i = 0; i < PATHNODESIZE; i++){ numExtern = 0; numIntern = 0; for(j = 0; j < 12; j++){ if(InfoForTileCars[i*12 + j].type == NodeTypeExtern) numExtern++; if(InfoForTileCars[i*12 + j].type == NodeTypeIntern) numIntern++; } if(numIntern > 1 && numExtern != 2) printf("ILLEGAL BLOCK. MORE THAN 1 INTERNALS AND NOT 2 EXTERNALS (Modelindex:%d)\n", i); } for(i = 0; i < PATHNODESIZE; i++) for(j = 0; j < 12; j++) if(InfoForTileCars[i*12 + j].type == NodeTypeExtern){ if(InfoForTileCars[i*12 + j].numLeftLanes < 0) printf("ILLEGAL BLOCK. NEGATIVE NUMBER OF LANES (Obj:%d)\n", i); if(InfoForTileCars[i*12 + j].numRightLanes < 0) printf("ILLEGAL BLOCK. NEGATIVE NUMBER OF LANES (Obj:%d)\n", i); if(InfoForTileCars[i*12 + j].numLeftLanes + InfoForTileCars[i*12 + j].numRightLanes <= 0) printf("ILLEGAL BLOCK. NO LANES IN NODE (Obj:%d)\n", i); } m_numPathNodes = 0; PreparePathDataForType(PathTypeCar, tempNodes, InfoForTileCars, 1.0f, DetachedNodesCars, 100); m_numCarPathNodes = m_numPathNodes; PreparePathDataForType(PathTypePed, tempNodes, InfoForTilePeds, 1.0f, DetachedNodesPeds, 50); m_numPedPathNodes = m_numPathNodes - m_numCarPathNodes; // TODO: figure out what exactly is going on here // Some roads seem to get a west/east flag for(i = 0; i < m_numMapObjects; i++){ numExtern = 0; numIntern = 0; numLanes = 0; maxX = 0.0f; maxY = 0.0f; for(j = 0; j < 12; j++){ k = i*12 + j; if(InfoForTileCars[k].type == NodeTypeExtern){ numExtern++; if(InfoForTileCars[k].numLeftLanes + InfoForTileCars[k].numRightLanes > numLanes) numLanes = InfoForTileCars[k].numLeftLanes + InfoForTileCars[k].numRightLanes; maxX = max(maxX, fabs(InfoForTileCars[k].x)); maxY = max(maxY, fabs(InfoForTileCars[k].y)); }else if(InfoForTileCars[k].type == NodeTypeIntern) numIntern++; } if(numIntern == 1 && numExtern == 2){ if(numLanes < 4){ if((i & 7) == 4){ // WHAT? m_objectFlags[i] |= PathNodeFlag1; if(maxX > maxY) m_objectFlags[i] |= PathNodeFlag2; else m_objectFlags[i] &= ~PathNodeFlag2; } }else{ m_objectFlags[i] |= PathNodeFlag1; if(maxX > maxY) m_objectFlags[i] |= PathNodeFlag2; else m_objectFlags[i] &= ~PathNodeFlag2; } } } delete[] tempNodes; CountFloodFillGroups(PathTypeCar); CountFloodFillGroups(PathTypePed); delete[] InfoForTileCars; InfoForTileCars = nil; delete[] InfoForTilePeds; InfoForTilePeds = nil; delete[] DetachedNodesCars; DetachedNodesCars = nil; delete[] DetachedNodesPeds; DetachedNodesPeds = nil; } printf("Done with PreparePathData\n"); } /* String together connected nodes in a list by a flood fill algorithm */ void CPathFind::CountFloodFillGroups(uint8 type) { int start, end; int i, l; uint16 n; CPathNode *node, *prev; switch(type){ case PathTypeCar: start = 0; end = m_numCarPathNodes; break; case PathTypePed: start = m_numCarPathNodes; end = start + m_numPedPathNodes; break; } for(i = start; i < end; i++) m_pathNodes[i].group = 0; n = 0; for(;;){ n++; if(n > 1500){ for(i = start; m_pathNodes[i].group && i < end; i++); printf("NumNodes:%d Accounted for:%d\n", end - start, i - start); } // Look for unvisited node for(i = start; m_pathNodes[i].group && i < end; i++); if(i == end) break; node = &m_pathNodes[i]; node->next = nil; node->group = n; if(node->numLinks == 0){ if(type == PathTypeCar) printf("Single car node: %f %f %f (%d)\n", node->pos.x, node->pos.y, node->pos.z, m_mapObjects[node->objectIndex]->m_modelIndex); else printf("Single ped node: %f %f %f\n", node->pos.x, node->pos.y, node->pos.z); } while(node){ prev = node; node = node->next; for(i = 0; i < prev->numLinks; i++){ l = m_connections[prev->firstLink + i]; if(m_pathNodes[l].group == 0){ m_pathNodes[l].group = n; if(m_pathNodes[l].group == 0) m_pathNodes[l].group = 0x80; // ??? m_pathNodes[l].next = node; node = &m_pathNodes[l]; } } } } m_numGroups[type] = n-1; printf("GraphType:%d. FloodFill groups:%d\n", type, n); } void CPathFind::PreparePathDataForType(uint8 type, CTempNode *tempnodes, CPathInfoForObject *objectpathinfo, float maxdist, CTempDetachedNode *detachednodes, int unused) { static CVector CoorsXFormed; int i, j, k, l; int l1, l2; int start, typeoff; float posx, posy; float dx, dy, mag; float nearestDist; int nearestId; int next; int oldNumPathNodes, oldNumLinks; CVector dist; int iseg, jseg; int istart, jstart; int done, cont; typeoff = 12*type; oldNumPathNodes = m_numPathNodes; oldNumLinks = m_numConnections; // Initialize map objects for(i = 0; i < m_numMapObjects; i++) for(j = 0; j < 12; j++) m_mapObjects[i]->m_nodeIndicesCars[typeoff + j] = -1; // Calculate internal nodes, store them and connect them to defining object for(i = 0; i < m_numMapObjects; i++){ start = 12*m_mapObjects[i]->m_modelIndex; for(j = 0; j < 12; j++){ if(objectpathinfo[start + j].type != NodeTypeIntern) continue; CalcNodeCoors( objectpathinfo[start + j].x, objectpathinfo[start + j].y, objectpathinfo[start + j].z, i, &CoorsXFormed); m_pathNodes[m_numPathNodes].pos = CoorsXFormed; m_pathNodes[m_numPathNodes].objectIndex = i; m_pathNodes[m_numPathNodes].flags |= PathNodeFlag1; m_mapObjects[i]->m_nodeIndicesCars[typeoff + j] = m_numPathNodes++; } } // Insert external nodes into TempList TempListLength = 0; for(i = 0; i < m_numMapObjects; i++){ start = 12*m_mapObjects[i]->m_modelIndex; for(j = 0; j < 12; j++){ if(objectpathinfo[start + j].type != NodeTypeExtern) continue; CalcNodeCoors( objectpathinfo[start + j].x, objectpathinfo[start + j].y, objectpathinfo[start + j].z, i, &CoorsXFormed); // find closest unconnected node nearestId = -1; nearestDist = maxdist; for(k = 0; k < TempListLength; k++){ if(tempnodes[k].linkState != 1) continue; dx = tempnodes[k].pos.x - CoorsXFormed.x; if(fabs(dx) < nearestDist){ dy = tempnodes[k].pos.y - CoorsXFormed.y; if(fabs(dy) < nearestDist){ nearestDist = max(fabs(dx), fabs(dy)); nearestId = k; } } } if(nearestId < 0){ // None found, add this one to temp list tempnodes[TempListLength].pos = CoorsXFormed; next = objectpathinfo[start + j].next; if(next < 0){ // no link from this node, find link to this node next = 0; for(k = start; j != objectpathinfo[k].next; k++) next++; } // link to connecting internal node tempnodes[TempListLength].link1 = m_mapObjects[i]->m_nodeIndicesCars[typeoff + next]; if(type == PathTypeCar){ tempnodes[TempListLength].numLeftLanes = objectpathinfo[start + j].numLeftLanes; tempnodes[TempListLength].numRightLanes = objectpathinfo[start + j].numRightLanes; } tempnodes[TempListLength++].linkState = 1; }else{ // Found nearest, connect it to our neighbour next = objectpathinfo[start + j].next; if(next < 0){ // no link from this node, find link to this node next = 0; for(k = start; j != objectpathinfo[k].next; k++) next++; } tempnodes[nearestId].link2 = m_mapObjects[i]->m_nodeIndicesCars[typeoff + next]; tempnodes[nearestId].linkState = 2; // collapse this node with nearest we found dx = m_pathNodes[tempnodes[nearestId].link1].pos.x - m_pathNodes[tempnodes[nearestId].link2].pos.x; dy = m_pathNodes[tempnodes[nearestId].link1].pos.y - m_pathNodes[tempnodes[nearestId].link2].pos.y; tempnodes[nearestId].pos = (tempnodes[nearestId].pos + CoorsXFormed)*0.5f; mag = sqrt(dx*dx + dy*dy); tempnodes[nearestId].dirX = dx/mag; tempnodes[nearestId].dirY = dy/mag; // do something when number of lanes doesn't agree if(type == PathTypeCar) if(tempnodes[nearestId].numLeftLanes != 0 && tempnodes[nearestId].numRightLanes != 0 && (objectpathinfo[start + j].numLeftLanes == 0 || objectpathinfo[start + j].numRightLanes == 0)){ // why switch left and right here? tempnodes[nearestId].numLeftLanes = objectpathinfo[start + j].numRightLanes; tempnodes[nearestId].numRightLanes = objectpathinfo[start + j].numLeftLanes; } } } } // Loop through previously added internal nodes and link them for(i = oldNumPathNodes; i < m_numPathNodes; i++){ // Init link m_pathNodes[i].numLinks = 0; m_pathNodes[i].firstLink = m_numConnections; // See if node connects to external nodes for(j = 0; j < TempListLength; j++){ if(tempnodes[j].linkState != 2) continue; // Add link to other side of the external if(tempnodes[j].link1 == i) m_connections[m_numConnections] = tempnodes[j].link2; else if(tempnodes[j].link2 == i) m_connections[m_numConnections] = tempnodes[j].link1; else continue; dist = m_pathNodes[i].pos - m_pathNodes[m_connections[m_numConnections]].pos; m_distances[m_numConnections] = dist.Magnitude(); m_connectionFlags[m_numConnections] = 0; if(type == PathTypeCar){ // IMPROVE: use a goto here // Find existing car path link for(k = 0; k < m_numCarPathLinks; k++){ if(m_carPathLinks[k].dirX == tempnodes[j].dirX && m_carPathLinks[k].dirY == tempnodes[j].dirY && m_carPathLinks[k].posX == tempnodes[j].pos.x && m_carPathLinks[k].posY == tempnodes[j].pos.y){ m_carPathConnections[m_numConnections] = k; k = m_numCarPathLinks; } } // k is m_numCarPathLinks+1 if we found one if(k == m_numCarPathLinks){ m_carPathLinks[m_numCarPathLinks].dirX = tempnodes[j].dirX; m_carPathLinks[m_numCarPathLinks].dirY = tempnodes[j].dirY; m_carPathLinks[m_numCarPathLinks].posX = tempnodes[j].pos.x; m_carPathLinks[m_numCarPathLinks].posY = tempnodes[j].pos.y; m_carPathLinks[m_numCarPathLinks].pathNodeIndex = i; m_carPathLinks[m_numCarPathLinks].numLeftLanes = tempnodes[j].numLeftLanes; m_carPathLinks[m_numCarPathLinks].numRightLanes = tempnodes[j].numRightLanes; m_carPathLinks[m_numCarPathLinks].trafficLightType = 0; m_carPathConnections[m_numConnections] = m_numCarPathLinks++; } } m_pathNodes[i].numLinks++; m_numConnections++; } // Find i inside path segment iseg = 0; for(j = max(oldNumPathNodes, i-12); j < i; j++) if(m_pathNodes[j].objectIndex == m_pathNodes[i].objectIndex) iseg++; istart = 12*m_mapObjects[m_pathNodes[i].objectIndex]->m_modelIndex; // Add links to other internal nodes for(j = max(oldNumPathNodes, i-12); j < min(m_numPathNodes, i+12); j++){ if(m_pathNodes[i].objectIndex != m_pathNodes[j].objectIndex || i == j) continue; // N.B.: in every path segment, the externals have to be at the end jseg = j-i + iseg; jstart = 12*m_mapObjects[m_pathNodes[j].objectIndex]->m_modelIndex; if(objectpathinfo[istart + iseg].next == jseg || objectpathinfo[jstart + jseg].next == iseg){ // Found a link between i and j m_connections[m_numConnections] = j; dist = m_pathNodes[i].pos - m_pathNodes[j].pos; m_distances[m_numConnections] = dist.Magnitude(); if(type == PathTypeCar){ posx = (m_pathNodes[i].pos.x + m_pathNodes[j].pos.x)*0.5f; posy = (m_pathNodes[i].pos.y + m_pathNodes[j].pos.y)*0.5f; dx = m_pathNodes[j].pos.x - m_pathNodes[i].pos.x; dy = m_pathNodes[j].pos.y - m_pathNodes[i].pos.y; mag = sqrt(dx*dx + dy*dy); dx /= mag; dy /= mag; if(i < j){ dx = -dx; dy = -dy; } // IMPROVE: use a goto here // Find existing car path link for(k = 0; k < m_numCarPathLinks; k++){ if(m_carPathLinks[k].dirX == dx && m_carPathLinks[k].dirY == dy && m_carPathLinks[k].posX == posx && m_carPathLinks[k].posY == posy){ m_carPathConnections[m_numConnections] = k; k = m_numCarPathLinks; } } // k is m_numCarPathLinks+1 if we found one if(k == m_numCarPathLinks){ m_carPathLinks[m_numCarPathLinks].dirX = dx; m_carPathLinks[m_numCarPathLinks].dirY = dy; m_carPathLinks[m_numCarPathLinks].posX = posx; m_carPathLinks[m_numCarPathLinks].posY = posy; m_carPathLinks[m_numCarPathLinks].pathNodeIndex = i; m_carPathLinks[m_numCarPathLinks].numLeftLanes = -1; m_carPathLinks[m_numCarPathLinks].numRightLanes = -1; m_carPathLinks[m_numCarPathLinks].trafficLightType = 0; m_carPathConnections[m_numConnections] = m_numCarPathLinks++; } }else{ // Crosses road if(objectpathinfo[istart + iseg].next == jseg && objectpathinfo[istart + iseg].flag & 1 || objectpathinfo[jstart + jseg].next == iseg && objectpathinfo[jstart + jseg].flag & 1) m_connectionFlags[m_numConnections] |= ConnectionCrossRoad; else m_connectionFlags[m_numConnections] &= ~ConnectionCrossRoad; } m_pathNodes[i].numLinks++; m_numConnections++; } } } if(type == PathTypeCar){ done = 0; // Set number of lanes for all nodes somehow // very strange code for(k = 0; !done && k < 10; k++){ done = 1; for(i = 0; i < m_numPathNodes; i++){ if(m_pathNodes[i].numLinks != 2) continue; l1 = m_carPathConnections[m_pathNodes[i].firstLink]; l2 = m_carPathConnections[m_pathNodes[i].firstLink+1]; if(m_carPathLinks[l1].numLeftLanes == -1 && m_carPathLinks[l2].numLeftLanes != -1){ done = 0; if(m_carPathLinks[l2].pathNodeIndex == i){ // why switch left and right here? m_carPathLinks[l1].numLeftLanes = m_carPathLinks[l2].numRightLanes; m_carPathLinks[l1].numRightLanes = m_carPathLinks[l2].numLeftLanes; }else{ m_carPathLinks[l1].numLeftLanes = m_carPathLinks[l2].numLeftLanes; m_carPathLinks[l1].numRightLanes = m_carPathLinks[l2].numRightLanes; } m_carPathLinks[l1].pathNodeIndex = i; }else if(m_carPathLinks[l1].numLeftLanes != -1 && m_carPathLinks[l2].numLeftLanes == -1){ done = 0; if(m_carPathLinks[l1].pathNodeIndex == i){ // why switch left and right here? m_carPathLinks[l2].numLeftLanes = m_carPathLinks[l1].numRightLanes; m_carPathLinks[l2].numRightLanes = m_carPathLinks[l1].numLeftLanes; }else{ m_carPathLinks[l2].numLeftLanes = m_carPathLinks[l1].numLeftLanes; m_carPathLinks[l2].numRightLanes = m_carPathLinks[l1].numRightLanes; } m_carPathLinks[l2].pathNodeIndex = i; }else if(m_carPathLinks[l1].numLeftLanes == -1 && m_carPathLinks[l2].numLeftLanes == -1) done = 0; } } // Fall back to default values for number of lanes for(i = 0; i < m_numPathNodes; i++) for(j = 0; j < m_pathNodes[i].numLinks; j++){ k = m_carPathConnections[m_pathNodes[i].firstLink + j]; if(m_carPathLinks[k].numLeftLanes < 0) m_carPathLinks[k].numLeftLanes = 1; if(m_carPathLinks[k].numRightLanes < 0) m_carPathLinks[k].numRightLanes = 1; } } // Set flags for car nodes if(type == PathTypeCar){ do{ cont = 0; for(i = 0; i < m_numPathNodes; i++){ m_pathNodes[i].flags &= ~PathNodeDisabled; m_pathNodes[i].flags &= ~PathNodeBetweenLevels; // See if node is a dead end, if so, we're not done yet if((m_pathNodes[i].flags & PathNodeDeadEnd) == 0){ k = 0; for(j = 0; j < m_pathNodes[i].numLinks; j++) if((m_pathNodes[m_connections[m_pathNodes[i].firstLink + j]].flags & PathNodeDeadEnd) == 0) k++; if(k < 2){ m_pathNodes[i].flags |= PathNodeDeadEnd; cont = 1; } } } }while(cont); } // Remove isolated ped nodes if(type == PathTypePed) for(i = oldNumPathNodes; i < m_numPathNodes; i++){ if(m_pathNodes[i].numLinks != 0) continue; // Remove node for(j = i; j < m_numPathNodes-1; j++) m_pathNodes[j] = m_pathNodes[j+1]; // Fix links for(j = oldNumLinks; j < m_numConnections; j++) if(m_connections[j] >= i) m_connections[j]--; // Also in treadables for(j = 0; j < m_numMapObjects; j++) for(k = 0; k < 12; k++){ if(m_mapObjects[j]->m_nodeIndicesPeds[k] == i){ // remove this one for(l = k; l < 12-1; l++) m_mapObjects[j]->m_nodeIndicesPeds[l] = m_mapObjects[j]->m_nodeIndicesPeds[l+1]; m_mapObjects[j]->m_nodeIndicesPeds[11] = -1; }else if(m_mapObjects[j]->m_nodeIndicesPeds[k] > i) m_mapObjects[j]->m_nodeIndicesPeds[k]--; } i--; m_numPathNodes--; } } void CPathFind::CalcNodeCoors(int16 x, int16 y, int16 z, int id, CVector *out) { CVector pos; pos.x = x / 16.0f; pos.y = y / 16.0f; pos.z = z / 16.0f; *out = m_mapObjects[id]->GetMatrix() * pos; } STARTPATCHES InjectHook(0x429610, &CPathFind::PreparePathData, PATCH_JUMP); InjectHook(0x429C20, &CPathFind::PreparePathDataForType, PATCH_JUMP); InjectHook(0x42B810, &CPathFind::CountFloodFillGroups, PATCH_JUMP); ENDPATCHES