changeset 138:a8ec7e1e18b6

Sliding downhill & relative subs. how it's done: each time you get a little bit pushed in the air along terrain normal, and then falling to the gravity, gradually sliding downwards. nice trick
author Nomad
date Tue, 12 Feb 2013 00:22:30 +0200
parents a2ddaf0e4d8a
children 7eeea515f5ff
files Outdoor.cpp mm7_3.cpp mm7_4.cpp mm7_data.cpp mm7_data.h
diffstat 5 files changed, 232 insertions(+), 246 deletions(-) [+]
line wrap: on
line diff
--- a/Outdoor.cpp	Mon Feb 11 20:27:00 2013 +0200
+++ b/Outdoor.cpp	Tue Feb 12 00:22:30 2013 +0200
@@ -315,7 +315,7 @@
   signed int v11; // eax@23
   int v12; // ST14_4@25
   signed int v14; // [sp-4h] [bp-84h]@6
-  char Str; // [sp+8h] [bp-78h]@3
+  char Str[140]; // [sp+8h] [bp-78h]@3
   int a5a; // [sp+94h] [bp+14h]@3
 
   auto Source = this;
@@ -323,9 +323,9 @@
   v5 = Source;
   if ( a5 < 10
     || strlen(Source->pLevelFilename) != 9
-    || (strcpy(&Str, v5->pLevelFilename),
-        _strlwr(&Str),
-        v6 = strtok(&Str, "out"),
+    || (strcpy(Str, v5->pLevelFilename),
+        _strlwr(Str),
+        v6 = strtok(Str, "out"),
         v6[2] = 0,
         v7 = atoi(v6),
         v8 = v7,
--- a/mm7_3.cpp	Mon Feb 11 20:27:00 2013 +0200
+++ b/mm7_3.cpp	Tue Feb 12 00:22:30 2013 +0200
@@ -995,7 +995,7 @@
     v74 = v4;
     if ( !v0->CanAct() )
       v74 = 0;
-    v70 = GetTerrainHeightsAroundParty(v0->vPosition.x, v0->vPosition.y);
+    v70 = IsTerrainSlopeTooHigh(v0->vPosition.x, v0->vPosition.y);
     v5 = sub_46D49E_prolly_get_world_y_under_party(
            v0->vPosition.x,
            v0->vPosition.y,
@@ -1088,7 +1088,7 @@
         v18 = v0->vPosition.y;
         v19 = v0->vPosition.x;
         v0->vPosition.z = v7;
-        _46DCC8_get_gravity_direction_outdoor(v19, v18, &v62);
+        ODM_GetTerrainNormalAt(v19, v18, &v62);
         v20 = GetGravityStrength();
         v21 = v62.y;
         v22 = v62.z;
@@ -1826,7 +1826,7 @@
   v1 = &pLayingItems[uLayingItemID];
   v58 = 0;
   v2 = &pObjectList->pObjects[v1->uObjectDescID];
-  v57 = GetTerrainHeightsAroundParty(v1->vPosition.x, v1->vPosition.y);
+  v57 = IsTerrainSlopeTooHigh(v1->vPosition.x, v1->vPosition.y);
   v3 = v1->vPosition.y;
   v4 = v1->vPosition.x;
   v5 = v2->uHeight;
@@ -1864,7 +1864,7 @@
       v11 = v1->vPosition.y;
       v12 = v1->vPosition.x;
       v1->vPosition.z = v8;
-      _46DCC8_get_gravity_direction_outdoor(v12, v11, &v51);
+      ODM_GetTerrainNormalAt(v12, v11, &v51);
       v1->vVelocity.z -= LOWORD(pEventTimer->uTimeElapsed) * GetGravityStrength();
       v56 = abs(v51.y * v1->vVelocity.y + v51.z * v1->vVelocity.z + v51.x * v1->vVelocity.x) >> 16;
       v60 = (Actor *)((unsigned __int64)(v56 * (signed __int64)v51.x) >> 16);
@@ -2994,7 +2994,7 @@
 //----- (00473893) --------------------------------------------------------
 void __cdecl ODM_ProcessPartyActions()
 {
-  int _zero; // esi@1
+  //int _zero; // esi@1
   int v1; // edi@1
   int v2; // ebx@1
   int v3; // eax@14
@@ -3092,9 +3092,10 @@
   int v95; // [sp-4h] [bp-98h]@104
   int v96; // [sp-4h] [bp-98h]@246
   int v97; // [sp+Ch] [bp-88h]@180
-  int v98; // [sp+10h] [bp-84h]@147
-  int v99; // [sp+14h] [bp-80h]@147
-  int v100; // [sp+18h] [bp-7Ch]@147
+  Vec3_int_ v98;
+  //int v98; // [sp+10h] [bp-84h]@147
+  //int v99; // [sp+14h] [bp-80h]@147
+  //int v100; // [sp+18h] [bp-7Ch]@147
   bool v101; // [sp+1Ch] [bp-78h]@33
   int v102; // [sp+20h] [bp-74h]@1
   int v103; // [sp+24h] [bp-70h]@1
@@ -3128,7 +3129,6 @@
 
   v121 = pParty->uFallSpeed;
   v123 = pParty->vPosition.z;
-  _zero = 0;
   v1 = 0;
   v103 = 0;
   v2 = 0;
@@ -3137,7 +3137,7 @@
   v117 = pParty->vPosition.y;
   v113 = pParty->field_6F0;
   bJumping = 0;
-  v118 = GetTerrainHeightsAroundParty(pParty->vPosition.x, pParty->vPosition.y);
+  auto partyAtHighSlope = IsTerrainSlopeTooHigh(pParty->vPosition.x, pParty->vPosition.y);
   v114 = 0;
   v124 = 0;
   v108 = 0;
@@ -3205,35 +3205,34 @@
         ++v4;
       }
       while ( (signed int)v4 <= (signed int)&pPlayers[4] );
-      _zero = 0;
     }
   }
   v109 = -1;
-  if ( pParty->bFlying != _zero )
+  if ( pParty->bFlying )
     v109 = sub_46D8E3(v116, v117, v123 + pParty->uPartyHeight, (int)&v102);
-  v107 = v108 == _zero;
+  v107 = v108 == 0;
   v105 = v111 + 1;
   if ( v123 <= v111 + 1 )
   {
     v109 = -1;
-    pParty->bFlying = _zero;
+    pParty->bFlying = false;
   }
   else
   {
     bJumping = 1;
   }
   v101 = v123 - v111 <= 32;
-  if ( bWalkSound != _zero && pParty->field_6F8 > _zero )
+  if ( bWalkSound && pParty->field_6F8 > 0 )
     pParty->field_6F8 -= pEventTimer->uTimeElapsed;
   if ( !bUnderwater
-    && SHIDWORD(pParty->pPartyBuffs[7].uExpireTime) <= _zero
-    && (SHIDWORD(pParty->pPartyBuffs[7].uExpireTime) < _zero || LODWORD(pParty->pPartyBuffs[7].uExpireTime) <= _zero) )
-    pParty->bFlying = _zero;
-  if ( bJumping == _zero )
+    && SHIDWORD(pParty->pPartyBuffs[7].uExpireTime) <= 0
+    && (SHIDWORD(pParty->pPartyBuffs[7].uExpireTime) < 0 || LODWORD(pParty->pPartyBuffs[7].uExpireTime) <= 0) )
+    pParty->bFlying = false;
+  if (!bJumping)
   {
     if ( pParty->field_6F4_packedid != (8 * v108 | OBJECT_BModel) )
     {
-      if ( v108 != _zero )
+      if (v108)
       {
         if ( v108 >> 6 < pOutdoor->uNumBModels )
         {
@@ -3269,12 +3268,14 @@
     switch ( pPartyActionQueue->Next() )
     {
       case PARTY_FlyUp:
-        if ( (signed __int64)pParty->pPartyBuffs[7].uExpireTime > 0 || bUnderwater == 1 )
-        {
-          pParty->bFlying = 0;
-          if ( bUnderwater
-            || pParty->pPartyBuffs[7].uFlags & 1
-            || pParty->pPlayers[pParty->pPartyBuffs[7].uCaster-1].sMana <= 0 )//*(int *)&pParty->pArtifactsFound[6972 * pParty->pPartyBuffs[7].uCaster + 10] > 0 )
+      {
+        if (!pParty->FlyActive() && !bUnderwater)
+          break;
+
+        pParty->bFlying = false;
+        if (bUnderwater ||
+            pParty->pPartyBuffs[PARTY_BUFF_FLY].uFlags & 1 ||
+            pParty->pPlayers[pParty->pPartyBuffs[PARTY_BUFF_FLY].uCaster - 1].sMana <= 0 )
           {
             if ( pParty->vPosition.z < 4000 || bJumping )
             {
@@ -3306,8 +3307,9 @@
               v127 = (BSPModel *)1;
             }
           }
-        }
-        goto LABEL_118;
+      }
+      break;
+
       case PARTY_FlyDown:
         if ( (signed __int64)pParty->pPartyBuffs[7].uExpireTime > 0 || bUnderwater == 1 )
         {
@@ -3456,7 +3458,7 @@
           v1 += v129;
           goto LABEL_93;
         }
-        if ( v118 && !v108 )
+        if ( partyAtHighSlope && !v108 )
         {
           v23 = stru_5C6E00->SinCos(_angle_y);
           v24 = (double)_walk_speed;
@@ -3548,7 +3550,7 @@
         _angle_x = 0;
         goto LABEL_118;
       case PARTY_Jump:
-        if ( (!v118 || v108) && !bJumping && pParty->field_24 && !(pParty->uFlags & 4) && !(BYTE1(pParty->uFlags) & 2) )
+        if ( (!partyAtHighSlope || v108) && !bJumping && pParty->field_24 && !(pParty->uFlags & 4) && !(BYTE1(pParty->uFlags) & 2) )
         {
           v126 = pParty->field_24 << 6;
           bJumping = 1;
@@ -3609,7 +3611,7 @@
     v121 += 2 * v33;
     goto LABEL_149;
   }
-  if ( !v118 )
+  if ( !partyAtHighSlope )
   {
     v34 = v121;
 LABEL_149:
@@ -3621,16 +3623,20 @@
   {
     if ( v108 )
       goto LABEL_150;
+
+    // rolling down the hill
+    // how it's done: you get a little bit pushed in the air along terrain normal, getting in the air
+    // and falling to the gravity, gradually sliding downwards. nice trick
     v123 = v111;
-    _46DCC8_get_gravity_direction_outdoor(v116, v117, (Vec3_int_ *)&v98);
+    ODM_GetTerrainNormalAt(v116, v117, &v98);
     v35 = v121 + -8 * pEventTimer->uTimeElapsed * GetGravityStrength();
-    v129 = abs((signed __int64)v2 * (signed __int64)v98 + (signed __int64)v1 * (signed __int64)v99 + (signed __int64)v35 * (signed __int64)v100) >> 16;
-    v127 = (BSPModel *)((unsigned __int64)(v129 * (signed __int64)v98) >> 16);
+    v129 = abs((signed __int64)v2 * v98.x + (signed __int64)v1 * v98.y + (signed __int64)v35 * v98.z) >> 16;
+    v127 = (BSPModel *)((unsigned __int64)(v129 * (signed __int64)v98.x) >> 16);
     v2 += (int)v127;
-    v127 = (BSPModel *)((unsigned __int64)(v129 * (signed __int64)v99) >> 16);
+    v127 = (BSPModel *)((unsigned __int64)(v129 * (signed __int64)v98.y) >> 16);
     v1 += (int)v127;
     v128 = v1;
-    v127 = (BSPModel *)((unsigned __int64)(v129 * (signed __int64)v100) >> 16);
+    v127 = (BSPModel *)((unsigned __int64)(v129 * (signed __int64)v98.z) >> 16);
     v34 = (int)((char *)v127 + v35);
     v32 = 0;
     v121 = v34;
@@ -3660,7 +3666,7 @@
 LABEL_150:
   pParty->uFallStartY = v123;
 LABEL_151:
-  if ( v2 * v2 + v1 * v1 < 400 && !v118 )
+  if ( v2 * v2 + v1 * v1 < 400 && !partyAtHighSlope )
   {
     *(float *)&v128 = 0.0;
     v2 = 0;
@@ -3720,8 +3726,8 @@
                         &v130,
                         &v110,
                         0);
-    v127 = (BSPModel *)GetTerrainHeightsAroundParty(_angle_x, v117);
-    v42 = GetTerrainHeightsAroundParty(v116, _angle_y);
+    v127 = (BSPModel *)IsTerrainSlopeTooHigh(_angle_x, v117);
+    v42 = IsTerrainSlopeTooHigh(v116, _angle_y);
     v107 = 0;
     v118 = v42;
     if ( !v97 && !v110 && !v108 )
@@ -3746,7 +3752,7 @@
                          &v130,
                          &v108,
                          0);
-    if ( GetTerrainHeightsAroundParty(_angle_x, _angle_y) && (signed int)v127 <= v123 )
+    if ( IsTerrainSlopeTooHigh(_angle_x, _angle_y) && (signed int)v127 <= v123 )
     {
       v43 = 1;
 LABEL_197:
@@ -9434,77 +9440,95 @@
 
 
 //----- (004823F4) --------------------------------------------------------
-bool __fastcall GetTerrainHeightsAroundParty(int a1, int a2)
-{
-  unsigned int v2; // ebx@1
-  unsigned int v3; // edi@1
-  int v4; // eax@1
-  int v6; // esi@5
-  int v7; // ecx@6
-  int v8; // edx@6
-  int v9; // eax@6
-  int v10; // esi@10
-  int v11; // [sp+14h] [bp-8h]@1
-  int v12; // [sp+18h] [bp-4h]@1
-
-  v12 = a1;
-  v11 = a2;
-  v2 = WorldPosToGridCellX(a1);
-  v3 = WorldPosToGridCellZ(v11) - 1;
-  dword_76D568_terrain_cell_world_pos_around_party_x = GridCellToWorldPosX(v2);
-  dword_76D56C_terrain_cell_world_pos_around_party_x = GridCellToWorldPosX(v2 + 1);
-  dword_76D570_terrain_cell_world_pos_around_party_x = GridCellToWorldPosX(v2 + 1);
-  dword_76D574_terrain_cell_world_pos_around_party_x = GridCellToWorldPosX(v2);
-  dword_76D558_terrain_cell_world_pos_around_party_z = GridCellToWorldPosZ(v3);
-  dword_76D55C_terrain_cell_world_pos_around_party_z = GridCellToWorldPosZ(v3);
-  dword_76D560_terrain_cell_world_pos_around_party_z = GridCellToWorldPosZ(v3 + 1);
-  dword_76D564_terrain_cell_world_pos_around_party_z = GridCellToWorldPosZ(v3 + 1);
-  dword_76D548_terrain_cell_world_pos_around_party_y = pOutdoor->DoGetHeightOnTerrain(v2, v3);
-  dword_76D54C_terrain_cell_world_pos_around_party_y = pOutdoor->DoGetHeightOnTerrain(v2 + 1, v3);
-  dword_76D550_terrain_cell_world_pos_around_party_y = pOutdoor->DoGetHeightOnTerrain(v2 + 1, v3 + 1);
-  v4 = pOutdoor->DoGetHeightOnTerrain(v2, v3 + 1);
-  dword_76D554_terrain_cell_world_pos_around_party_y = v4;
-  if ( dword_76D548_terrain_cell_world_pos_around_party_y == dword_76D54C_terrain_cell_world_pos_around_party_y
-    && dword_76D54C_terrain_cell_world_pos_around_party_y == dword_76D550_terrain_cell_world_pos_around_party_y
-    && dword_76D550_terrain_cell_world_pos_around_party_y == v4 )
-    return 0;
-  v6 = abs(v12 - dword_76D568_terrain_cell_world_pos_around_party_x);
-  if ( abs(dword_76D558_terrain_cell_world_pos_around_party_z - v11) >= v6 )
-  {
-    v7 = dword_76D554_terrain_cell_world_pos_around_party_y;
-    v8 = dword_76D550_terrain_cell_world_pos_around_party_y;
-    v9 = dword_76D548_terrain_cell_world_pos_around_party_y;
-  }
-  else
-  {
-    v7 = dword_76D54C_terrain_cell_world_pos_around_party_y;
-    v8 = dword_76D548_terrain_cell_world_pos_around_party_y;
-    v9 = dword_76D550_terrain_cell_world_pos_around_party_y;
-  }
-  if ( v7 >= v8 )
-  {
-    v10 = v8;
-    if ( v8 < v9 )
+bool IsTerrainSlopeTooHigh(int pos_x, int pos_z)
+{
+  //unsigned int v2; // ebx@1
+  //unsigned int v3; // edi@1
+  //int v4; // eax@1
+  //int v6; // esi@5
+  //int v7; // ecx@6
+  //int v8; // edx@6
+  //int v9; // eax@6
+  //int v10; // esi@10
+  //int v11; // [sp+14h] [bp-8h]@1
+  //int v12; // [sp+18h] [bp-4h]@1
+
+  //v12 = a1;
+  //v11 = a2;
+  auto grid_x = WorldPosToGridCellX(pos_x);
+  auto grid_z = WorldPosToGridCellZ(pos_z) - 1;
+
+  auto party_grid_x1 = GridCellToWorldPosX(grid_x);
+  //dword_76D56C_terrain_cell_world_pos_around_party_x = GridCellToWorldPosX(grid_x + 1);
+  //dword_76D570_terrain_cell_world_pos_around_party_x = GridCellToWorldPosX(grid_x + 1);
+  //dword_76D574_terrain_cell_world_pos_around_party_x = GridCellToWorldPosX(grid_x);
+  auto party_grid_z1 = GridCellToWorldPosZ(grid_z);
+  //dword_76D55C_terrain_cell_world_pos_around_party_z = GridCellToWorldPosZ(grid_z);
+  //dword_76D560_terrain_cell_world_pos_around_party_z = GridCellToWorldPosZ(grid_z + 1);
+  //dword_76D564_terrain_cell_world_pos_around_party_z = GridCellToWorldPosZ(grid_z + 1);
+  auto party_x1z1_y = pOutdoor->DoGetHeightOnTerrain(grid_x, grid_z);
+  auto party_x2z1_y = pOutdoor->DoGetHeightOnTerrain(grid_x + 1, grid_z);
+  auto party_x2z2_y = pOutdoor->DoGetHeightOnTerrain(grid_x + 1, grid_z + 1);
+  auto party_x1z2_y = pOutdoor->DoGetHeightOnTerrain(grid_x, grid_z + 1);
+  //dword_76D554_terrain_cell_world_pos_around_party_y = v4;
+  if (party_x1z1_y == party_x2z1_y &&
+      party_x2z1_y == party_x2z2_y &&
+      party_x2z2_y == party_x1z2_y )
+    return false;
+
+  auto dx = abs(pos_x - party_grid_x1),
+       dz = abs(party_grid_z1 - pos_z);
+
+  int y1, y2, y3;
+  if (dz >= dx)
+  {
+    y1 = party_x1z2_y;  //  lower-left triangle
+    y2 = party_x2z2_y;  //  y3 | \ 
+    y3 = party_x1z1_y;  //     |   \ 
+                        /*     |     \ 
+                               |______ \
+                            y1           y2   */
+  }
+  else
+  {
+    y1 = party_x2z1_y;  // upper-right
+    y2 = party_x1z1_y;  //  y2_______ y1
+    y3 = party_x2z2_y;  //    \     |
+                        /*      \   |
+                                  \ |
+                                    y3     */
+  }
+
+  int y_min = min(y1, min(y2, y3)),
+      y_max = max(y1, max(y2, y3));
+  return y_max - y_min > 512;
+
+  /*if ( y1 >= y2 )
+  {
+    y_min = y2;
+    if ( y2 < y3 )
       goto LABEL_13;
 LABEL_12:
-    v10 = v9;
+    y_min = y3;
     goto LABEL_13;
   }
-  if ( v7 >= v9 )
+  else if ( y1 >= y3 )
     goto LABEL_12;
-  v10 = v7;
+  else
+  y_min = y1;
+
 LABEL_13:
-  if ( v7 <= v8 )
-  {
-    if ( v8 > v9 )
-      v9 = v8;
-  }
-  else
-  {
-    if ( v7 > v9 )
-      v9 = v7;
-  }
-  return v9 - v10 > 512;
+  if ( y1 <= y2 )
+  {
+    if ( y2 > y3 )
+      y3 = y2;
+  }
+  else
+  {
+    if ( y1 > y3 )
+      y3 = y1;
+  }
+  return y3 - v10 > 512;*/
 }
 
 
--- a/mm7_4.cpp	Mon Feb 11 20:27:00 2013 +0200
+++ b/mm7_4.cpp	Tue Feb 12 00:22:30 2013 +0200
@@ -977,118 +977,80 @@
   return dword_720F20[v22];
 }
 
+
+//not sure if right- or left-handed coordinate space assumed, so this could be normal of inverse normal
+// for a right-handed system, that would be an inverse normal
 //----- (0046DCC8) --------------------------------------------------------
-signed __int64 __fastcall _46DCC8_get_gravity_direction_outdoor(int a1, int a2, Vec3_int_ *a3)
-{
-  int v3; // ebx@1
-  int v4; // ST4C_4@1
-  unsigned int v5; // ST54_4@1
-  unsigned int v6; // edi@1
-  unsigned int v7; // ST50_4@1
-  int v8; // edi@1
-  int v9; // esi@1
-  int v10; // ebx@1
-  int v11; // ecx@2
-  int v12; // eax@2
-  int v13; // edx@2
-  int v14; // ebx@2
-  double v15; // st7@4
-  double v16; // st6@4
-  double v17; // st5@4
-  float v18; // ST44_4@4
-  float v19; // ST54_4@4
-  double v20; // st4@4
-  double v21; // st5@4
-  float v22; // ST44_4@6
-  double v23; // st7@4
-  double v24; // st7@6
-  signed __int64 result; // qax@6
-  int v26; // [sp+14h] [bp-44h]@1
-  int v27; // [sp+18h] [bp-40h]@1
-  int v28; // [sp+20h] [bp-38h]@1
-  int v29; // [sp+24h] [bp-34h]@1
-  int v30; // [sp+28h] [bp-30h]@1
-  int v31; // [sp+2Ch] [bp-2Ch]@1
-  int v32; // [sp+30h] [bp-28h]@1
-  int v33; // [sp+34h] [bp-24h]@1
-  int v34; // [sp+38h] [bp-20h]@1
-  int v35; // [sp+3Ch] [bp-1Ch]@1
-  int v36; // [sp+40h] [bp-18h]@1
-  int v37; // [sp+4Ch] [bp-Ch]@2
-  float v38; // [sp+4Ch] [bp-Ch]@4
-  int v39; // [sp+50h] [bp-8h]@2
-  float v40; // [sp+50h] [bp-8h]@4
-  int v41; // [sp+54h] [bp-4h]@2
-
-  v3 = a1;
-  v4 = a2;
-  v5 = WorldPosToGridCellX(a1);
-  v6 = WorldPosToGridCellZ(v4) - 1;
-  v33 = GridCellToWorldPosX(v5);
-  v34 = GridCellToWorldPosX(v5 + 1);
-  v35 = GridCellToWorldPosX(v5 + 1);
-  v36 = GridCellToWorldPosX(v5);
-  v29 = GridCellToWorldPosZ(v6);
-  v30 = GridCellToWorldPosZ(v6);
-  v7 = v6 + 1;
-  v31 = GridCellToWorldPosZ(v6 + 1);
-  v32 = GridCellToWorldPosZ(v6 + 1);
-  v26 = pOutdoor->DoGetHeightOnTerrain(v5, v6);
-  v27 = pOutdoor->DoGetHeightOnTerrain(v5 + 1, v6);
-  v8 = pOutdoor->DoGetHeightOnTerrain(v5 + 1, v6 + 1);
-  v28 = pOutdoor->DoGetHeightOnTerrain(v5, v7);
-  v9 = v29;
-  v10 = abs(v3 - v33);
-  if ( abs(v29 - v4) >= v10 )
-  {
-    v11 = v33;
-    v37 = v28;
-    v39 = v36;
-    v12 = v35;
-    v41 = v32;
-    v13 = v31;
-    v14 = v8;
-    v8 = v26;
-  }
-  else
-  {
-    v11 = v35;
-    v41 = v30;
-    v39 = v34;
-    v12 = v33;
-    v13 = v29;
-    v9 = v31;
-    v37 = v27;
-    v14 = v26;
-  }
-  v15 = (double)(v12 - v39);
-  v16 = (double)(v13 - v41);
-  v17 = (double)(v14 - v37);
-  v18 = (double)(v11 - v39);
-  v19 = (double)(v9 - v41);
-  v20 = (double)(v8 - v37);
-  v38 = v20 * v16 - v19 * v17;
-  v40 = v18 * v17 - v20 * v15;
-  v21 = v19 * v15 - v18 * v16;
-  v23 = sqrt(v21 * v21 + v40 * v40 + v38 * v38);
-  if ( v23 == 0.0 )
-  {
-    a3->y = 0;
-    a3->x = 0;
-    a3->z = 65536;
-  }
-  v24 = 1.0 / v23;
-  a3->x = (signed __int64)(v24 * v38 * 65536.0);
-  a3->y = (signed __int64)(v24 * v40 * 65536.0);
-  v22 = v21;
-  result = (signed __int64)(v24 * v22 * 65536.0);
-  a3->z = result;
-  return result;
-}
-// 47F44B: using guessed type int __stdcall WorldPosToGridCellX(int);
-// 47F458: using guessed type int __stdcall WorldPosToGridCellZ(int);
-// 47F469: using guessed type int __stdcall GridCellToWorldPosX(int);
-// 47F476: using guessed type int __stdcall GridCellToWorldPosZ(int);
+void ODM_GetTerrainNormalAt(int pos_x, int pos_z, Vec3_int_ *out)
+{
+  auto grid_x = WorldPosToGridCellX(pos_x);
+  auto grid_z = WorldPosToGridCellZ(pos_z) - 1;
+
+  auto grid_pos_x1 = GridCellToWorldPosX(grid_x);
+  auto grid_pos_x2 = GridCellToWorldPosX(grid_x + 1);
+  auto grid_pos_z1 = GridCellToWorldPosZ(grid_z);
+  auto grid_pos_z2 = GridCellToWorldPosZ(grid_z + 1);
+
+  auto x1z1_y = pOutdoor->DoGetHeightOnTerrain(grid_x, grid_z);
+  auto x2z1_y = pOutdoor->DoGetHeightOnTerrain(grid_x + 1, grid_z);
+  auto x2z2_y = pOutdoor->DoGetHeightOnTerrain(grid_x + 1, grid_z + 1);
+  auto x1z2_y = pOutdoor->DoGetHeightOnTerrain(grid_x, grid_z + 1);
+
+  float side1_dx, side1_dy, side1_dz,
+        side2_dx, side2_dy, side2_dz;
+
+  auto dx = abs(pos_x - grid_pos_x1),
+       dz = abs(grid_pos_z1 - pos_z);
+  if (dz >= dx)
+  {
+    side1_dy = (double)(x1z1_y - x1z2_y);
+    side2_dy = (double)(x2z2_y - x1z2_y);
+    side2_dx = (double)(grid_pos_x2 - grid_pos_x1);
+    side1_dx = (double)(grid_pos_x1 - grid_pos_x1);
+    side2_dz = (double)(grid_pos_z2 - grid_pos_z2);  // bug?  z2 - z2
+    side1_dz = (double)(grid_pos_z2 - grid_pos_z2);  //       z1 - z2
+    Log::Warning(L"%s %s %u\n", __FILE__, __FUNCTION__, __LINE__);
+    /*       |\
+       side1 |  \
+             |____\
+             side 2      */
+  }
+  else
+  {
+    side1_dy = (double)(x2z2_y - x2z1_y);
+    side2_dy = (double)(x1z1_y - x2z1_y);
+    side2_dx = (double)(grid_pos_x1 - grid_pos_x2);
+    side1_dx = (double)(grid_pos_x2 - grid_pos_x2);
+    side2_dz = (double)(grid_pos_z1 - grid_pos_z1);
+    side1_dz = (double)(grid_pos_z2 - grid_pos_z1);
+
+    /*   side 2
+         _____
+         \    |
+           \  | side 1
+             \|       */
+  }
+  
+  float nx = side1_dy * side2_dz - side1_dz * side2_dy;
+  float ny = side1_dx * side2_dy - side1_dy * side2_dx;
+  float nz = side1_dz * side2_dx - side1_dx * side2_dz;
+
+  float mag = sqrt(nx * nx + ny * ny + nz * nz);
+  if (fabsf(mag) < 1e-6f)
+  {
+    out->y = 0;
+    out->x = 0;
+    out->z = 65536;
+  }
+  else
+  {
+    float invmag = 1.0 / mag;
+    out->x = invmag * nx * 65536.0;
+    out->y = invmag * ny * 65536.0;
+    out->z = invmag * nz * 65536.0;
+  }
+}
+
 
 //----- (0046DEF2) --------------------------------------------------------
 unsigned int __fastcall sub_46DEF2(signed int a2, unsigned int uLayingItemID)
--- a/mm7_data.cpp	Mon Feb 11 20:27:00 2013 +0200
+++ b/mm7_data.cpp	Tue Feb 12 00:22:30 2013 +0200
@@ -1763,18 +1763,18 @@
 int dword_76D53C_terrain_cell_world_pos_around_party_x; // weak
 int dword_76D540_terrain_cell_world_pos_around_party_x; // weak
 int dword_76D544_terrain_cell_world_pos_around_party_x; // weak
-int dword_76D548_terrain_cell_world_pos_around_party_y; // weak
-int dword_76D54C_terrain_cell_world_pos_around_party_y; // weak
-int dword_76D550_terrain_cell_world_pos_around_party_y; // weak
-int dword_76D554_terrain_cell_world_pos_around_party_y; // weak
-int dword_76D558_terrain_cell_world_pos_around_party_z; // weak
-int dword_76D55C_terrain_cell_world_pos_around_party_z; // weak
-int dword_76D560_terrain_cell_world_pos_around_party_z; // weak
-int dword_76D564_terrain_cell_world_pos_around_party_z; // weak
-int dword_76D568_terrain_cell_world_pos_around_party_x; // weak
-int dword_76D56C_terrain_cell_world_pos_around_party_x; // weak
-int dword_76D570_terrain_cell_world_pos_around_party_x; // weak
-int dword_76D574_terrain_cell_world_pos_around_party_x; // weak
+//int dword_76D548_terrain_cell_world_pos_around_party_y; // weak
+//int dword_76D54C_terrain_cell_world_pos_around_party_y; // weak
+//int dword_76D550_terrain_cell_world_pos_around_party_y; // weak
+//int dword_76D554_terrain_cell_world_pos_around_party_y; // weak
+//int dword_76D558_terrain_cell_world_pos_around_party_z; // weak
+//int dword_76D55C_terrain_cell_world_pos_around_party_z; // weak
+//int dword_76D560_terrain_cell_world_pos_around_party_z; // weak
+//int dword_76D564_terrain_cell_world_pos_around_party_z; // weak
+//int dword_76D568_terrain_cell_world_pos_around_party_x; // weak
+//int dword_76D56C_terrain_cell_world_pos_around_party_x; // weak
+//int dword_76D570_terrain_cell_world_pos_around_party_x; // weak
+//int dword_76D574_terrain_cell_world_pos_around_party_x; // weak
 struct unk_F7B60C stru_76D578; // struct @ MM7.exe::0076D578
 struct unk_F7B60C stru_76D590; // struct @ MM7.exe::0076D590
 struct unk_F7B60C stru_76D5A8; // struct @ MM7.exe::0076D5A8
--- a/mm7_data.h	Mon Feb 11 20:27:00 2013 +0200
+++ b/mm7_data.h	Tue Feb 12 00:22:30 2013 +0200
@@ -1608,18 +1608,18 @@
 extern int dword_76D53C_terrain_cell_world_pos_around_party_x; // weak
 extern int dword_76D540_terrain_cell_world_pos_around_party_x; // weak
 extern int dword_76D544_terrain_cell_world_pos_around_party_x; // weak
-extern int dword_76D548_terrain_cell_world_pos_around_party_y; // weak
-extern int dword_76D54C_terrain_cell_world_pos_around_party_y; // weak
-extern int dword_76D550_terrain_cell_world_pos_around_party_y; // weak
-extern int dword_76D554_terrain_cell_world_pos_around_party_y; // weak
-extern int dword_76D558_terrain_cell_world_pos_around_party_z; // weak
-extern int dword_76D55C_terrain_cell_world_pos_around_party_z; // weak
-extern int dword_76D560_terrain_cell_world_pos_around_party_z; // weak
-extern int dword_76D564_terrain_cell_world_pos_around_party_z; // weak
-extern int dword_76D568_terrain_cell_world_pos_around_party_x; // weak
-extern int dword_76D56C_terrain_cell_world_pos_around_party_x; // weak
-extern int dword_76D570_terrain_cell_world_pos_around_party_x; // weak
-extern int dword_76D574_terrain_cell_world_pos_around_party_x; // weak
+//extern int dword_76D548_terrain_cell_world_pos_around_party_y; // weak
+//extern int dword_76D54C_terrain_cell_world_pos_around_party_y; // weak
+//extern int dword_76D550_terrain_cell_world_pos_around_party_y; // weak
+//extern int dword_76D554_terrain_cell_world_pos_around_party_y; // weak
+//extern int dword_76D558_terrain_cell_world_pos_around_party_z; // weak
+//extern int dword_76D55C_terrain_cell_world_pos_around_party_z; // weak
+//extern int dword_76D560_terrain_cell_world_pos_around_party_z; // weak
+//extern int dword_76D564_terrain_cell_world_pos_around_party_z; // weak
+//extern int dword_76D568_terrain_cell_world_pos_around_party_x; // weak
+//extern int dword_76D56C_terrain_cell_world_pos_around_party_x; // weak
+//extern int dword_76D570_terrain_cell_world_pos_around_party_x; // weak
+//extern int dword_76D574_terrain_cell_world_pos_around_party_x; // weak
 extern char byte_76D5C0;
 extern struct unk_F7B60C stru_76D578;
 extern struct unk_F7B60C stru_76D590;
@@ -2163,7 +2163,7 @@
 int _46CEC3_get_floor_level(int x, int y, int z, unsigned int uSectorID, unsigned int *pFaceID);
 int __fastcall sub_46D49E_prolly_get_world_y_under_party(int a1, signed int a2, int a3, int a4, int *a5, int *a6, int a7);
 int __fastcall sub_46D8E3(int a1, signed int a2, int a3, int a4);
-signed __int64 __fastcall _46DCC8_get_gravity_direction_outdoor(int a1, int a2, Vec3_int_ *a3);
+void ODM_GetTerrainNormalAt(int pos_x, int pos_z, Vec3_int_ *out);
 unsigned int __fastcall sub_46DEF2(signed int a2, unsigned int uLayingItemID);
 int __fastcall _46DF1A_collide_against_actor(int, int); // weak
 void __cdecl _46E0B2_collide_against_decorations();
@@ -2225,7 +2225,7 @@
 void __cdecl sub_481ED9_MessWithOutdoorCamera();
 bool __fastcall sub_481EFA(RenderVertexSoft *a1, RenderVertexSoft *a2, RenderVertexSoft *a3, RenderVertexSoft *a4, int a5);
 signed int __fastcall sub_481FC9(RenderVertexSoft *_ECX, RenderVertexSoft *a2, RenderVertexSoft *a3, stru148 *a4);
-bool __fastcall GetTerrainHeightsAroundParty(int a1, int a2);
+bool IsTerrainSlopeTooHigh(int pos_x, int pos_y);
 int __fastcall GetTerrainHeightsAroundParty2(int a1, int a2, int *a3, int a4);
 struct stru148 *__fastcall sr_sub_4829B9(RenderVertexSoft *a1, RenderVertexSoft *a2, RenderVertexSoft *a3, stru148 *a4, int a5);
 signed int __cdecl const_1_0();