summaryrefslogtreecommitdiffstats
path: root/src/control
diff options
context:
space:
mode:
authoraap <aap@papnet.eu>2019-05-30 21:24:47 +0200
committeraap <aap@papnet.eu>2019-05-30 21:24:47 +0200
commit188aab4196c1d9de0c1bf33be1114e7a0e11fd19 (patch)
treeff0c0a98ced0ba0bbabf34f5f6b1c0e152dcb194 /src/control
parentfixed ped states (diff)
downloadre3-188aab4196c1d9de0c1bf33be1114e7a0e11fd19.tar
re3-188aab4196c1d9de0c1bf33be1114e7a0e11fd19.tar.gz
re3-188aab4196c1d9de0c1bf33be1114e7a0e11fd19.tar.bz2
re3-188aab4196c1d9de0c1bf33be1114e7a0e11fd19.tar.lz
re3-188aab4196c1d9de0c1bf33be1114e7a0e11fd19.tar.xz
re3-188aab4196c1d9de0c1bf33be1114e7a0e11fd19.tar.zst
re3-188aab4196c1d9de0c1bf33be1114e7a0e11fd19.zip
Diffstat (limited to 'src/control')
-rw-r--r--src/control/Darkel.cpp5
-rw-r--r--src/control/Darkel.h7
-rw-r--r--src/control/Garages.cpp44
-rw-r--r--src/control/Garages.h8
-rw-r--r--src/control/PathFind.cpp594
-rw-r--r--src/control/PathFind.h129
-rw-r--r--src/control/Pickups.cpp7
-rw-r--r--src/control/Pickups.h13
-rw-r--r--src/control/Replay.cpp5
-rw-r--r--src/control/Replay.h7
10 files changed, 819 insertions, 0 deletions
diff --git a/src/control/Darkel.cpp b/src/control/Darkel.cpp
new file mode 100644
index 00000000..95f3e176
--- /dev/null
+++ b/src/control/Darkel.cpp
@@ -0,0 +1,5 @@
+#include "common.h"
+#include "patcher.h"
+#include "Darkel.h"
+
+WRAPPER void CDarkel::DrawMessages(void) { EAXJMP(0x420920); }
diff --git a/src/control/Darkel.h b/src/control/Darkel.h
new file mode 100644
index 00000000..41cc69f8
--- /dev/null
+++ b/src/control/Darkel.h
@@ -0,0 +1,7 @@
+#pragma once
+
+class CDarkel
+{
+public:
+ static void DrawMessages(void);
+};
diff --git a/src/control/Garages.cpp b/src/control/Garages.cpp
new file mode 100644
index 00000000..effc13d8
--- /dev/null
+++ b/src/control/Garages.cpp
@@ -0,0 +1,44 @@
+#include "common.h"
+#include "patcher.h"
+#include "ModelIndices.h"
+#include "Garages.h"
+
+WRAPPER void CGarages::PrintMessages(void) { EAXJMP(0x426310); }
+
+bool
+CGarages::IsModelIndexADoor(uint32 id)
+{
+ return id == MI_GARAGEDOOR1 ||
+ id == MI_GARAGEDOOR2 ||
+ id == MI_GARAGEDOOR3 ||
+ id == MI_GARAGEDOOR4 ||
+ id == MI_GARAGEDOOR5 ||
+ id == MI_GARAGEDOOR6 ||
+ id == MI_GARAGEDOOR7 ||
+ id == MI_GARAGEDOOR9 ||
+ id == MI_GARAGEDOOR10 ||
+ id == MI_GARAGEDOOR11 ||
+ id == MI_GARAGEDOOR12 ||
+ id == MI_GARAGEDOOR13 ||
+ id == MI_GARAGEDOOR14 ||
+ id == MI_GARAGEDOOR15 ||
+ id == MI_GARAGEDOOR16 ||
+ id == MI_GARAGEDOOR17 ||
+ id == MI_GARAGEDOOR18 ||
+ id == MI_GARAGEDOOR19 ||
+ id == MI_GARAGEDOOR20 ||
+ id == MI_GARAGEDOOR21 ||
+ id == MI_GARAGEDOOR22 ||
+ id == MI_GARAGEDOOR23 ||
+ id == MI_GARAGEDOOR24 ||
+ id == MI_GARAGEDOOR25 ||
+ id == MI_GARAGEDOOR26 ||
+ id == MI_GARAGEDOOR27 ||
+ id == MI_GARAGEDOOR28 ||
+ id == MI_GARAGEDOOR29 ||
+ id == MI_GARAGEDOOR30 ||
+ id == MI_GARAGEDOOR31 ||
+ id == MI_GARAGEDOOR32 ||
+ id == MI_CRUSHERBODY ||
+ id == MI_CRUSHERLID;
+}
diff --git a/src/control/Garages.h b/src/control/Garages.h
new file mode 100644
index 00000000..87cf47fa
--- /dev/null
+++ b/src/control/Garages.h
@@ -0,0 +1,8 @@
+#pragma once
+
+class CGarages
+{
+public:
+ static bool IsModelIndexADoor(uint32 id);
+ static void PrintMessages(void);
+};
diff --git a/src/control/PathFind.cpp b/src/control/PathFind.cpp
new file mode 100644
index 00000000..8857f8c9
--- /dev/null
+++ b/src/control/PathFind.cpp
@@ -0,0 +1,594 @@
+#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
diff --git a/src/control/PathFind.h b/src/control/PathFind.h
new file mode 100644
index 00000000..495c4a73
--- /dev/null
+++ b/src/control/PathFind.h
@@ -0,0 +1,129 @@
+#pragma once
+
+#include "Treadable.h"
+
+struct CPathNode
+{
+ CVector pos;
+ CPathNode *prev; //?
+ CPathNode *next;
+ int16 unknown;
+ int16 objectIndex;
+ int16 firstLink;
+ uint8 numLinks;
+ uint8 flags;
+ uint8 group;
+/* VC:
+ int16 unk1;
+ int16 nextIndex;
+ int16 x;
+ int16 y;
+ int16 z;
+ int16 unknown;
+ int16 firstLink;
+ int8 width;
+ int8 group;
+ int8 numLinks : 4;
+ int8 bDeadEnd : 1;
+ int8 bTurnedOff : 1; // flag 8 in node info
+ int8 flagA40 : 1; // flag 20 in node info
+ int8 flagA80 : 1; // flag 4 in node info
+ int8 flagB1 : 1; // flag 10 in node info
+ int8 flagB2 : 1; // flag 2 in node info
+ int8 flagB4 : 1;
+ int8 speedLimit : 2; // speed limit
+ int8 flagB20 : 1;
+ int8 flagB40 : 1;
+ int8 flagB80 : 1;
+ int8 spawnRate : 4;
+ int8 flagsC : 4;
+*/
+};
+
+struct CCarPathLink
+{
+ float posX;
+ float posY;
+ float dirX;
+ float dirY;
+ int16 pathNodeIndex;
+ int8 numLeftLanes;
+ int8 numRightLanes;
+ int8 trafficLightType;
+ int8 field15;
+ // probably only padding
+ int8 field16;
+ int8 field17;
+};
+
+struct CPathInfoForObject
+{
+ int16 x;
+ int16 y;
+ int16 z;
+ int8 type;
+ int8 next;
+ int8 numLeftLanes;
+ int8 numRightLanes;
+ uint8 flag;
+};
+
+struct CTempNode
+{
+ CVector pos;
+ float dirX;
+ float dirY;
+ int16 link1;
+ int16 link2;
+ int8 numLeftLanes;
+ int8 numRightLanes;
+ int8 linkState;
+ // probably padding
+ int8 field1B;
+};
+
+struct CTempDetachedNode // unused
+{
+ uint8 foo[20];
+};
+
+class CPathFind
+{
+public:
+/* For reference VC:
+ CPathNode pathNodes[9650];
+ CCarPathLink m_carPathLinks[3500];
+ CBuilding *m_mapObjects[1250];
+ // 0x8000 is cross road flag
+ // 0x4000 is traffic light flag
+ uint16 m_connections[20400];
+ uint8 m_distances[20400];
+ int16 m_carPathConnections[20400];
+*/
+ CPathNode m_pathNodes[4930];
+ CCarPathLink m_carPathLinks[2076];
+ CTreadable *m_mapObjects[1250];
+ uint8 m_objectFlags[1250];
+ int16 m_connections[10260];
+ int16 m_distances[10260];
+ uint8 m_connectionFlags[10260];
+ int16 m_carPathConnections[10260];
+ int32 m_numPathNodes;
+ int32 m_numCarPathNodes;
+ int32 m_numPedPathNodes;
+ int16 m_numMapObjects;
+ int16 m_numConnections;
+ int32 m_numCarPathLinks;
+ int32 h;
+ uint8 m_numGroups[2];
+ CPathNode m_aExtraPaths[872];
+
+ void PreparePathData(void);
+ void CountFloodFillGroups(uint8 type);
+ void PreparePathDataForType(uint8 type, CTempNode *tempnodes, CPathInfoForObject *objectpathinfo,
+ float unk, CTempDetachedNode *detachednodes, int unused);
+ void CalcNodeCoors(int16 x, int16 y, int16 z, int32 id, CVector *out);
+};
+static_assert(sizeof(CPathFind) == 0x4c8f4, "CPathFind: error");
+
+extern CPathFind &ThePaths;
diff --git a/src/control/Pickups.cpp b/src/control/Pickups.cpp
new file mode 100644
index 00000000..63aaa08e
--- /dev/null
+++ b/src/control/Pickups.cpp
@@ -0,0 +1,7 @@
+#include "common.h"
+#include "patcher.h"
+#include "Pickups.h"
+
+WRAPPER void CPickups::RenderPickUpText(void) { EAXJMP(0x432440); }
+
+WRAPPER void CPacManPickups::Render(void) { EAXJMP(0x432F60); }
diff --git a/src/control/Pickups.h b/src/control/Pickups.h
new file mode 100644
index 00000000..d2c3628c
--- /dev/null
+++ b/src/control/Pickups.h
@@ -0,0 +1,13 @@
+#pragma once
+
+class CPickups
+{
+public:
+ static void RenderPickUpText(void);
+};
+
+class CPacManPickups
+{
+public:
+ static void Render(void);
+};
diff --git a/src/control/Replay.cpp b/src/control/Replay.cpp
new file mode 100644
index 00000000..32e7dc0c
--- /dev/null
+++ b/src/control/Replay.cpp
@@ -0,0 +1,5 @@
+#include "common.h"
+#include "patcher.h"
+#include "Replay.h"
+
+WRAPPER void CReplay::Display(void) { EAXJMP(0x595EE0); } \ No newline at end of file
diff --git a/src/control/Replay.h b/src/control/Replay.h
new file mode 100644
index 00000000..77e50403
--- /dev/null
+++ b/src/control/Replay.h
@@ -0,0 +1,7 @@
+#pragma once
+
+class CReplay
+{
+public:
+ static void Display(void);
+};