Subversion Repositories Games.Carmageddon

Rev

Rev 15 | Go to most recent revision | Blame | Compare with Previous | Last modification | View Log | Download | RSS feed

  1. #include "crush.h"
  2. #include "brender.h"
  3. #include "car.h"
  4. #include "displays.h"
  5. #include "globvars.h"
  6. #include "globvrkm.h"
  7. #include "globvrpb.h"
  8. #include "graphics.h"
  9. #include "harness/trace.h"
  10. #include "loading.h"
  11. #include "mainloop.h"
  12. #include "netgame.h"
  13. #include "network.h"
  14. #include "oil.h"
  15. #include "opponent.h"
  16. #include "pd/sys.h"
  17. #include "pedestrn.h"
  18. #include "piping.h"
  19. #include "pratcam.h"
  20. #include "raycast.h"
  21. #include "replay.h"
  22. #include "spark.h"
  23. #include "structur.h"
  24. #include "utility.h"
  25. #include "world.h"
  26. #include <stdlib.h>
  27.  
  28. float gWobble_spam_y[8] = { 0.0f, -0.15f, 0.4f, 0.15f, -0.4f, 0.25f, 0.0f, -0.25f };
  29. float gWobble_spam_z[8] = { 0.4f, -0.25f, 0.0f, 0.25f, 0.0f, 0.15f, -0.4f, -0.15f };
  30. br_scalar gWheel_circ_to_width = 0.16f;
  31. tU8 gSmoke_damage_step[12] = { 20u, 20u, 0u, 10u, 10u, 10u, 10u, 10u, 10u, 10u, 10u, 10u };
  32. int gSteal_ranks[5] = { 89, 72, 55, 38, 21 };
  33.  
  34. #define BIGAPC_OPPONENT_INDEX 4
  35.  
  36. // IDA: int __usercall ReadCrushData@<EAX>(FILE *pF@<EAX>, tCrush_data *pCrush_data@<EDX>)
  37. int ReadCrushData(FILE* pF, tCrush_data* pCrush_data) {
  38.     //char s[256]; // Pierre-Marie Baty -- unused variable
  39.     //char* str; // Pierre-Marie Baty -- unused variable
  40.     int i;
  41.     int j;
  42.     //int k; // Pierre-Marie Baty -- unused variable
  43.     tCrush_point_spec* the_spec;
  44.     tCrush_neighbour* the_neighbour;
  45.     LOG_TRACE("(%p, %p)", pF, pCrush_data);
  46.  
  47.     pCrush_data->softness_factor = GetAFloat(pF);
  48.     GetPairOfFloats(pF, &pCrush_data->min_fold_factor, &pCrush_data->max_fold_factor);
  49.     pCrush_data->wibble_factor = GetAFloat(pF);
  50.     pCrush_data->limit_deviant = GetAFloat(pF);
  51.     pCrush_data->split_chance = GetAFloat(pF);
  52.     pCrush_data->min_y_fold_down = GetAFloat(pF);
  53.     pCrush_data->number_of_crush_points = GetAnInt(pF);
  54.     pCrush_data->crush_points = (tCrush_point_spec*)BrMemAllocate(sizeof(tCrush_point_spec) * pCrush_data->number_of_crush_points, kMem_crush_data);
  55.  
  56.     for (i = 0, the_spec = pCrush_data->crush_points; i < pCrush_data->number_of_crush_points; i++, the_spec++) {
  57.         the_spec->vertex_index = GetAnInt(pF);
  58.         GetThreeFloats(pF, &the_spec->limits_neg.v[0], &the_spec->limits_neg.v[1], &the_spec->limits_neg.v[2]);
  59.         GetThreeFloats(pF, &the_spec->limits_pos.v[0], &the_spec->limits_pos.v[1], &the_spec->limits_pos.v[2]);
  60.         GetThreeFloats(pF, &the_spec->softness_neg.v[0], &the_spec->softness_neg.v[1], &the_spec->softness_neg.v[2]);
  61.         GetThreeFloats(pF, &the_spec->softness_pos.v[0], &the_spec->softness_pos.v[1], &the_spec->softness_pos.v[2]);
  62.         the_spec->number_of_neighbours = GetAnInt(pF);
  63.         the_spec->neighbours = BrMemAllocate(sizeof(tCrush_neighbour) * the_spec->number_of_neighbours, kMem_crush_neighbours);
  64.  
  65.         for (j = 0, the_neighbour = the_spec->neighbours; j < the_spec->number_of_neighbours; j++, the_neighbour++) {
  66.             the_neighbour->vertex_index = GetAnInt(pF);
  67.             the_neighbour->factor = GetAnInt(pF);
  68.         }
  69.     }
  70.     return 0;
  71. }
  72.  
  73. // IDA: float __usercall SkipCrushData@<ST0>(FILE *pF@<EAX>)
  74. float SkipCrushData(FILE* pF) {
  75.     int i;
  76.     int j;
  77.     int count_1;
  78.     int count_2;
  79.     char s[256];
  80.     float softness;
  81.     LOG_TRACE("(%p)", pF);
  82.  
  83.     softness = GetAFloat(pF);
  84.     for (i = 0; i < 5; ++i) {
  85.         GetALineAndDontArgue(pF, s);
  86.     }
  87.     count_1 = GetAnInt(pF);
  88.     for (i = 0; i < count_1; i++) {
  89.         for (j = 0; j < 5; j++) {
  90.             GetALineAndDontArgue(pF, s);
  91.         }
  92.         count_2 = GetAnInt(pF);
  93.         for (j = 0; j < 2 * count_2; j++) {
  94.             GetALineAndDontArgue(pF, s);
  95.         }
  96.     }
  97.     return softness;
  98. }
  99.  
  100. // IDA: int __usercall WriteCrushData@<EAX>(FILE *pF@<EAX>, tCrush_data *pCrush_data@<EDX>)
  101. int WriteCrushData(FILE* pF, tCrush_data* pCrush_data) {
  102.     int i;
  103.     int j;
  104.     //int k; // Pierre-Marie Baty -- unused variable
  105.     tCrush_point_spec* the_spec;
  106.     tCrush_neighbour* the_neighbour;
  107.     LOG_TRACE("(%p, %p)", pF, pCrush_data);
  108.  
  109.     fprintf(pF, "%f\n\r", pCrush_data->softness_factor);
  110.     fprintf(pF, "%f,%f\n\r", pCrush_data->min_fold_factor, pCrush_data->max_fold_factor);
  111.     fprintf(pF, "%f\n\r", pCrush_data->wibble_factor);
  112.     fprintf(pF, "%f\n\r", pCrush_data->limit_deviant);
  113.     fprintf(pF, "%f\n\r", pCrush_data->split_chance);
  114.     fprintf(pF, "%f\n\r", pCrush_data->min_y_fold_down);
  115.     fprintf(pF, "%d\n\r", pCrush_data->number_of_crush_points);
  116.     for (i = 0, the_spec = pCrush_data->crush_points; i < pCrush_data->number_of_crush_points; i++, the_spec++) {
  117.         fprintf(pF, "%d\n\r", the_spec->vertex_index);
  118.         fprintf(pF, "%f, %f, %f\n\r", the_spec->limits_neg.v[0], the_spec->limits_neg.v[1], the_spec->limits_neg.v[2]);
  119.         fprintf(pF, "%f, %f, %f\n\r", the_spec->limits_pos.v[0], the_spec->limits_pos.v[1], the_spec->limits_pos.v[2]);
  120.         fprintf(pF, "%f, %f, %f\n\r", the_spec->softness_neg.v[0], the_spec->softness_neg.v[1], the_spec->softness_neg.v[2]);
  121.         fprintf(pF, "%f, %f, %f\n\r", the_spec->softness_pos.v[0], the_spec->softness_pos.v[1], the_spec->softness_pos.v[2]);
  122.         fprintf(pF, "%d\n\r", the_spec->number_of_neighbours);
  123.         for (j = 0, the_neighbour = the_spec->neighbours; j < the_spec->number_of_neighbours; j++, the_neighbour++) {
  124.             fprintf(pF, "%d\n\r", the_neighbour->vertex_index);
  125.             fprintf(pF, "%d\n\r", the_neighbour->factor);
  126.         }
  127.     }
  128.     return 0;
  129. }
  130.  
  131. // IDA: void __usercall DisposeCrushData(tCrush_data *pCrush_data@<EAX>)
  132. void DisposeCrushData(tCrush_data* pCrush_data) {
  133.     int i;
  134.     LOG_TRACE("(%p)", pCrush_data);
  135.  
  136.     for (i = 0; i < pCrush_data->number_of_crush_points; i++) {
  137.         if (pCrush_data->crush_points[i].neighbours != NULL) {
  138.             BrMemFree(pCrush_data->crush_points[i].neighbours);
  139.         }
  140.     }
  141.     if (pCrush_data->crush_points != NULL) {
  142.         BrMemFree(pCrush_data->crush_points);
  143.     }
  144. }
  145.  
  146. // IDA: void __usercall CrushModelPoint(tCar_spec *pCar@<EAX>, int pModel_index@<EDX>, br_model *pModel@<EBX>, int pCrush_point_index@<ECX>, br_vector3 *pEnergy_vector, br_scalar total_energy, tCrush_data *pCrush_data)
  147. void CrushModelPoint(tCar_spec* pCar, int pModel_index, br_model* pModel, int pCrush_point_index, br_vector3* pEnergy_vector, br_scalar total_energy, tCrush_data* pCrush_data) {
  148.     int i;
  149.     int j;
  150.     //int k; // Pierre-Marie Baty -- unused variable
  151.     int pipe_vertex_count;
  152.     int neighbour_index;
  153.     int bend_axis;
  154.     int default_bend_axis[3];
  155.     tCrush_point_spec* the_crush_point;
  156.     tCrush_neighbour* the_neighbour;
  157.     br_vector3* target_point;
  158.     br_vector3 old_vector;
  159.     br_vector3 softnesss;
  160.     br_vector3 movement;
  161.     br_scalar random_range;
  162.     br_scalar bend_amount;
  163.     br_scalar min_y_fold_down;
  164.     float default_bend_factor[3];
  165.     float working_min_fold;
  166.     float working_max_fold;
  167.     float working_wibble;
  168.     float working_limit_deviant;
  169.     float working_split_chance;
  170.     tChanged_vertex pipe_array[600];
  171.     //tCar_spec* car; // Pierre-Marie Baty -- unused variable
  172.     LOG_TRACE("(%p, %d, %p, %d, %p, %f, %p)", pCar, pModel_index, pModel, pCrush_point_index, pEnergy_vector, total_energy, pCrush_data);
  173.  
  174.     pipe_vertex_count = 0;
  175.     if (gNet_mode == eNet_mode_host && pCar->car_model_actors[pModel_index].min_distance_squared == 0.0f) {
  176.         NetSendPointCrush(pCar, pCrush_point_index, pEnergy_vector);
  177.     }
  178.     working_min_fold = pCrush_data->min_fold_factor * gCar_crush_min_fold;
  179.     working_max_fold = pCrush_data->max_fold_factor * gCar_crush_max_fold;
  180.     working_wibble = pCrush_data->wibble_factor * gCar_crush_wibble;
  181.     working_limit_deviant = pCrush_data->limit_deviant * gCar_crush_limit_deviant;
  182.     working_split_chance = pCrush_data->split_chance * gCar_crush_split_chance;
  183.     min_y_fold_down = pCrush_data->min_y_fold_down;
  184.     the_crush_point = &pCrush_data->crush_points[pCrush_point_index];
  185.     if (pModel->nvertices <= the_crush_point->vertex_index) {
  186.         return;
  187.     }
  188.     target_point = &pModel->vertices[the_crush_point->vertex_index].p;
  189.     old_vector = *target_point;
  190.     for (i = 0; i < 3; i++) {
  191.         pEnergy_vector->v[i] = SRandomPosNeg(working_wibble * total_energy) + pEnergy_vector->v[i];
  192.         random_range = (the_crush_point->limits_pos.v[i] - the_crush_point->limits_neg.v[i]) * working_limit_deviant;
  193.         if (pEnergy_vector->v[i] >= 0.0f) {
  194.             softnesss.v[i] = the_crush_point->softness_pos.v[i];
  195.         } else {
  196.             softnesss.v[i] = the_crush_point->softness_neg.v[i];
  197.         }
  198.         movement.v[i] = target_point->v[i];
  199.         target_point->v[i] += pEnergy_vector->v[i] * softnesss.v[i];
  200.         if (the_crush_point->limits_neg.v[i] <= target_point->v[i]) {
  201.             if (target_point->v[i] > the_crush_point->limits_pos.v[i]) {
  202.                 target_point->v[i] = SRandomPosNeg(random_range) + the_crush_point->limits_pos.v[i];
  203.             }
  204.         } else {
  205.             target_point->v[i] = SRandomPosNeg(random_range) + the_crush_point->limits_neg.v[i];
  206.         }
  207.         movement.v[i] = target_point->v[i] - movement.v[i];
  208.         if (pEnergy_vector->v[i] * movement.v[i] < 0.0f) {
  209.             movement.v[i] = 0.0f;
  210.             target_point->v[i] = old_vector.v[i];
  211.         }
  212.     }
  213.  
  214.     if (IsActionReplayAvailable()) {
  215.         pipe_array[pipe_vertex_count].vertex_index = the_crush_point->vertex_index;
  216.         BrVector3Sub(&pipe_array[pipe_vertex_count].delta_coordinates, target_point, &old_vector);
  217.         pipe_vertex_count++;
  218.     }
  219.     neighbour_index = -1;
  220.     for (bend_axis = 0; bend_axis < 3; bend_axis++) {
  221.         default_bend_axis[bend_axis] = (bend_axis + IRandomBetween(1, 2)) % 3;
  222.         default_bend_factor[bend_axis] = FRandomBetween(working_min_fold, working_max_fold);
  223.     }
  224.  
  225.     the_neighbour = the_crush_point->neighbours;
  226.     for (j = 0; j < the_crush_point->number_of_neighbours; j++, the_neighbour++) {
  227.         if (the_neighbour->vertex_index) {
  228.             neighbour_index += the_neighbour->vertex_index;
  229.             if (neighbour_index < 0 || pModel->nvertices <= neighbour_index) {
  230.                 return;
  231.             }
  232.             target_point = &pModel->vertices[neighbour_index].p;
  233.             old_vector = *target_point;
  234.             for (bend_axis = 0; bend_axis < 3; bend_axis++) {
  235.                 target_point->v[bend_axis] += (1.0f - the_neighbour->factor / 256.0f) * movement.v[bend_axis];
  236.                 float v12;
  237.                 if (the_neighbour->factor <= 128) {
  238.                     v12 = the_neighbour->factor / 128.0f;
  239.                 } else {
  240.                     v12 = 2.0 - the_neighbour->factor / 128.0f;
  241.                 }
  242.                 if (((int)((target_point->v[2] + target_point->v[1] + target_point->v[0]) * 63.0f) & 1) * v12 == 0.0) {
  243.                     bend_amount = -default_bend_factor[bend_axis];
  244.                 } else {
  245.                     bend_amount = default_bend_factor[bend_axis];
  246.                 }
  247.                 int axis_tmp = (((int)((target_point->v[2] + target_point->v[1] + target_point->v[0]) * 100.0f) + bend_axis - 1) & 1) % 3;
  248.                 target_point->v[axis_tmp] += fabs(movement.v[bend_axis]) * bend_amount;
  249.             }
  250.             if (IsActionReplayAvailable() && pipe_vertex_count < 600) {
  251.                 pipe_array[pipe_vertex_count].vertex_index = neighbour_index;
  252.                 BrVector3Sub(&pipe_array[pipe_vertex_count].delta_coordinates, target_point, &old_vector);
  253.                 pipe_vertex_count++;
  254.             }
  255.         } else {
  256.             neighbour_index += the_neighbour->factor;
  257.         }
  258.     }
  259.     if (IsActionReplayAvailable() && pipe_vertex_count) {
  260.         PipeSingleModelGeometry(pCar->car_ID, pModel_index, pipe_vertex_count, pipe_array);
  261.     }
  262. }
  263.  
  264. // IDA: void __usercall CrushModel(tCar_spec *pCar@<EAX>, int pModel_index@<EDX>, br_actor *pActor@<EBX>, br_vector3 *pImpact_point@<ECX>, br_vector3 *pEnergy_vector, tCrush_data *pCrush_data)
  265. void CrushModel(tCar_spec* pCar, int pModel_index, br_actor* pActor, br_vector3* pImpact_point, br_vector3* pEnergy_vector, tCrush_data* pCrush_data) {
  266.     br_scalar this_distance;
  267.     br_scalar total_energy;
  268.     br_scalar nearest_so_far;
  269.     br_vector3 impact_point_model;
  270.     br_vector3 energy_vector_scaled;
  271.     br_vector3 energy_vector_model;
  272.     int i;
  273.     int nearest_index;
  274.     br_vertex* vertices;
  275.     br_vertex* the_vertex;
  276.     //br_matrix34 inverse_transform; // Pierre-Marie Baty -- unused variable
  277.     LOG_TRACE("(%p, %d, %p, %p, %p, %p)", pCar, pModel_index, pActor, pImpact_point, pEnergy_vector, pCrush_data);
  278.  
  279.     if (gArrow_mode) {
  280.         return;
  281.     }
  282.     if (pCrush_data->number_of_crush_points == 0) {
  283.         return;
  284.     }
  285.     BrVector3Sub(&impact_point_model, pImpact_point, (br_vector3*)pActor->t.t.mat.m[3]);
  286.     BrVector3Scale(&energy_vector_model, pEnergy_vector, pCrush_data->softness_factor * gCar_crush_softness);
  287.     total_energy = BrVector3Length(&energy_vector_model);
  288.     if (total_energy < 0.06f) {
  289.         return;
  290.     }
  291.     BrVector3Scale(&energy_vector_scaled, &energy_vector_model, (total_energy - 0.06f) / total_energy);
  292.     nearest_so_far = BR_SCALAR_MAX;
  293.     vertices = pActor->model->vertices;
  294.     nearest_index = -1;
  295.     for (i = 0; i < pCrush_data->number_of_crush_points; i++) {
  296.         the_vertex = &vertices[pCrush_data->crush_points[i].vertex_index];
  297.         this_distance = (impact_point_model.v[2] - the_vertex->p.v[2]) * (impact_point_model.v[2] - the_vertex->p.v[2]) + (impact_point_model.v[1] - the_vertex->p.v[1]) * (impact_point_model.v[1] - the_vertex->p.v[1]) + (impact_point_model.v[0] - the_vertex->p.v[0]) * (impact_point_model.v[0] - the_vertex->p.v[0]);
  298.         if (this_distance < nearest_so_far) {
  299.             nearest_so_far = this_distance;
  300.             nearest_index = i;
  301.         }
  302.     }
  303.     if (nearest_index >= 0) {
  304.         CrushModelPoint(pCar, pModel_index, pActor->model, nearest_index, &energy_vector_scaled, total_energy, pCrush_data);
  305.         SetModelForUpdate(pActor->model, pCar, 1);
  306.     }
  307. }
  308.  
  309. // IDA: void __cdecl JitModelUpdate(br_actor *actor, br_model *model, br_material *material, void *render_data, br_uint_8 style, int on_screen)
  310. void JitModelUpdate(br_actor* actor, br_model* model, br_material* material, void* render_data, br_uint_8 style, int on_screen) {
  311.     LOG_TRACE("(%p, %p, %p, %p, %d, %d)", actor, model, material, render_data, style, on_screen);
  312.  
  313.     BrModelUpdate(model, BR_MODU_VERTEX_POSITIONS);
  314.     model->flags &= ~(BR_MODF_CUSTOM);
  315.     BrZbModelRender(actor, model, material, style, BrOnScreenCheck(&model->bounds), 0);
  316. }
  317.  
  318. // IDA: void __usercall SetModelForUpdate(br_model *pModel@<EAX>, tCar_spec *pCar@<EDX>, int crush_only@<EBX>)
  319. void SetModelForUpdate(br_model* pModel, tCar_spec* pCar, int crush_only) {
  320.     LOG_TRACE("(%p, %p, %d)", pModel, pCar, crush_only);
  321.  
  322.     if (crush_only && pCar != NULL && pCar->car_model_actors[pCar->principal_car_actor].actor->model == pModel) {
  323.         CrushBoundingBox(pCar, crush_only);
  324.     }
  325.     if ((pModel->flags & BR_MODF_CUSTOM) != 0) {
  326.         pModel->user = JitModelUpdate;
  327.     } else {
  328.         pModel->custom = JitModelUpdate;
  329.         pModel->flags |= BR_MODF_CUSTOM;
  330.     }
  331. }
  332.  
  333. // IDA: void __usercall TotallySpamTheModel(tCar_spec *pCar@<EAX>, int pModel_index@<EDX>, br_actor *pActor@<EBX>, tCrush_data *pCrush_data@<ECX>, br_scalar pMagnitude)
  334. void TotallySpamTheModel(tCar_spec* pCar, int pModel_index, br_actor* pActor, tCrush_data* pCrush_data, br_scalar pMagnitude) {
  335.     //br_scalar total_energy; // Pierre-Marie Baty -- unused variable
  336.     br_vector3 energy_vector_model;
  337.     int i;
  338.     int the_index;
  339.     br_vertex* the_vertex;
  340.     //br_vertex* vertices; // Pierre-Marie Baty -- unused variable
  341.     LOG_TRACE("(%p, %d, %p, %p, %f)", pCar, pModel_index, pActor, pCrush_data, pMagnitude);
  342.  
  343.     if (gArrow_mode || pCrush_data->number_of_crush_points == 0) {
  344.         return;
  345.     }
  346.     the_vertex = pActor->model->vertices;
  347.     for (i = 0; i < 15; i++) {
  348.         the_index = IRandomBetween(0, pCrush_data->number_of_crush_points - 1);
  349.         energy_vector_model = the_vertex[pCrush_data->crush_points[the_index].vertex_index].p;
  350.         BrVector3Normalise(&energy_vector_model, &energy_vector_model);
  351.         BrVector3Scale(&energy_vector_model, &energy_vector_model, -pMagnitude);
  352.         CrushModelPoint(pCar, pModel_index, pActor->model, the_index, &energy_vector_model, pMagnitude, pCrush_data);
  353.     }
  354.     SetModelForUpdate(pActor->model, pCar, 1);
  355. }
  356.  
  357. // IDA: br_scalar __usercall RepairModel@<ST0>(tCar_spec *pCar@<EAX>, int pModel_index@<EDX>, br_actor *pActor@<EBX>, br_vertex *pUndamaged_vertices@<ECX>, br_scalar pAmount, br_scalar *pTotal_deflection)
  358. br_scalar RepairModel(tCar_spec* pCar, int pModel_index, br_actor* pActor, br_vertex* pUndamaged_vertices, br_scalar pAmount, br_scalar* pTotal_deflection) {
  359.     int i;
  360.     int j;
  361.     int pipe_vertex_count;
  362.     br_vector3 old_point;
  363.     br_vertex* model_vertex;
  364.     br_scalar amount;
  365.     //br_scalar deviation; // Pierre-Marie Baty -- unused variable
  366.     tChanged_vertex pipe_array[600];
  367.     LOG_TRACE("(%p, %d, %p, %p, %f, %p)", pCar, pModel_index, pActor, pUndamaged_vertices, pAmount, pTotal_deflection);
  368.  
  369.     pipe_vertex_count = 0;
  370.     amount = 0.0f;
  371.     *pTotal_deflection = 0.0f;
  372.  
  373.     for (i = 0; i < pActor->model->nvertices; i++) {
  374.         model_vertex = &pActor->model->vertices[i];
  375.         old_point = model_vertex->p;
  376.         for (j = 0; j < 3; ++j) {
  377.             *pTotal_deflection = fabsf(pUndamaged_vertices->p.v[j] - old_point.v[j]) + *pTotal_deflection;
  378.             if (pUndamaged_vertices->p.v[j] >= old_point.v[j]) {
  379.                 if (pUndamaged_vertices->p.v[j] > old_point.v[j]) {
  380.                     model_vertex->p.v[j] = model_vertex->p.v[j] + pAmount;
  381.                     if (pUndamaged_vertices->p.v[j] < model_vertex->p.v[j]) {
  382.                         model_vertex->p.v[j] = pUndamaged_vertices->p.v[j];
  383.                     }
  384.                     amount = model_vertex->p.v[j] - old_point.v[j] + amount;
  385.                 }
  386.             } else {
  387.                 model_vertex->p.v[j] = model_vertex->p.v[j] - pAmount;
  388.                 if (pUndamaged_vertices->p.v[j] > model_vertex->p.v[j]) {
  389.                     model_vertex->p.v[j] = pUndamaged_vertices->p.v[j];
  390.                 }
  391.                 amount = old_point.v[j] - model_vertex->p.v[j] + amount;
  392.             }
  393.         }
  394.         if (amount != 0.0 && IsActionReplayAvailable() && pipe_vertex_count < COUNT_OF(pipe_array)) {
  395.             pipe_array[pipe_vertex_count].vertex_index = i;
  396.             BrVector3Sub(&pipe_array[pipe_vertex_count].delta_coordinates, &model_vertex->p, &old_point);
  397.             pipe_vertex_count++;
  398.         }
  399.         pUndamaged_vertices++;
  400.     }
  401.     SetModelForUpdate(pActor->model, pCar, 0);
  402.     if (IsActionReplayAvailable() && pipe_vertex_count) {
  403.         PipeSingleModelGeometry(pCar->car_ID, pModel_index, pipe_vertex_count, pipe_array);
  404.     }
  405.     return amount;
  406. }
  407.  
  408. // IDA: float __usercall RepairCar2@<ST0>(tCar_spec *pCar@<EAX>, tU32 pFrame_period@<EDX>, br_scalar *pTotal_deflection@<EBX>)
  409. float RepairCar2(tCar_spec* pCar, tU32 pFrame_period, br_scalar* pTotal_deflection) {
  410.     int i;
  411.     tCar_actor* the_car_actor;
  412.     br_scalar amount;
  413.     br_scalar dummy;
  414.     LOG_TRACE("(%p, %d, %p)", pCar, pFrame_period, pTotal_deflection);
  415.  
  416.     if (gArrow_mode) {
  417.         return 0.0f;
  418.     }
  419.     *pTotal_deflection = 0.0;
  420.     amount = 0.0;
  421.  
  422.     for (i = 0; i < gProgram_state.current_car.car_actor_count; i++) {
  423.         the_car_actor = &pCar->car_model_actors[i];
  424.         if (the_car_actor->min_distance_squared == 0.0 || !the_car_actor->undamaged_vertices) {
  425.             if (the_car_actor->undamaged_vertices) {
  426.                 amount = RepairModel(pCar, i, the_car_actor->actor, the_car_actor->undamaged_vertices, pFrame_period * 0.00005f, pTotal_deflection);
  427.             }
  428.         } else {
  429.             RepairModel(pCar, i, the_car_actor->actor, the_car_actor->undamaged_vertices, pFrame_period * 0.00005f, &dummy);
  430.         }
  431.     }
  432.     pCar->repair_time += pFrame_period;
  433.     return amount;
  434. }
  435.  
  436. // IDA: float __usercall RepairCar@<ST0>(tU16 pCar_ID@<EAX>, tU32 pFrame_period@<EDX>, br_scalar *pTotal_deflection@<EBX>)
  437. float RepairCar(tU16 pCar_ID, tU32 pFrame_period, br_scalar* pTotal_deflection) {
  438.     LOG_TRACE("(%d, %d, %p)", pCar_ID, pFrame_period, pTotal_deflection);
  439.  
  440.     if (VEHICLE_TYPE_FROM_ID(pCar_ID) == eVehicle_self) {
  441.         return RepairCar2(&gProgram_state.current_car, pFrame_period, pTotal_deflection);
  442.     }
  443.  
  444.     return RepairCar2(GetCarSpec(VEHICLE_TYPE_FROM_ID(pCar_ID), VEHICLE_INDEX_FROM_ID(pCar_ID)), pFrame_period, pTotal_deflection);
  445. }
  446.  
  447. // IDA: void __usercall TotallyRepairACar(tCar_spec *pCar@<EAX>)
  448. void TotallyRepairACar(tCar_spec* pCar) {
  449.     int i;
  450.     int j;
  451.     int k;
  452.     int pipe_vertex_count;
  453.     tCar_actor* the_car_actor;
  454.     tChanged_vertex pipe_array[600];
  455.     br_bounds storage_bounds;
  456.     LOG_TRACE("(%p)", pCar);
  457.  
  458.     StopCarSmokingInstantly(pCar);
  459.     if (IsActionReplayAvailable()) {
  460.         PipeInstantUnSmudge(pCar);
  461.     }
  462.     pCar->repair_time += 100000;
  463.     for (i = 0; i < COUNT_OF(pCar->damage_units); i++) {
  464.         pCar->damage_units[i].damage_level = 0;
  465.         pCar->damage_units[i].last_level = 0;
  466.         pCar->damage_units[i].smoke_last_level = 0;
  467.     }
  468.     memcpy(&storage_bounds, &pCar->bounds[1], sizeof(br_bounds));
  469.     memcpy(&pCar->bounds[1], &pCar->max_bounds[1], sizeof(br_bounds));
  470.     if (TestForCarInSensiblePlace(pCar)) {
  471.         for (j = 0; j < pCar->car_actor_count; j++) {
  472.             the_car_actor = &pCar->car_model_actors[j];
  473.             if (the_car_actor->undamaged_vertices != NULL) {
  474.                 pipe_vertex_count = 0;
  475.                 for (k = 0; k < the_car_actor->actor->model->nvertices; k++) {
  476.                     if (pipe_vertex_count < COUNT_OF(pipe_array)) {
  477.                         BrVector3Sub(&pipe_array[pipe_vertex_count].delta_coordinates,
  478.                             &the_car_actor->undamaged_vertices[k].p, &the_car_actor->actor->model->vertices[k].p);
  479.                         if (!Vector3IsZero(&pipe_array[pipe_vertex_count].delta_coordinates)) {
  480.                             pipe_array[pipe_vertex_count].vertex_index = k;
  481.                             pipe_vertex_count++;
  482.                         }
  483.                     }
  484.                 }
  485.                 memcpy(the_car_actor->actor->model->vertices,
  486.                     the_car_actor->undamaged_vertices,
  487.                     the_car_actor->actor->model->nvertices * sizeof(br_vertex));
  488.                 BrModelUpdate(the_car_actor->actor->model, BR_MODU_VERTEX_COLOURS | BR_MODU_VERTEX_POSITIONS);
  489.                 if (pipe_vertex_count != 0 && IsActionReplayAvailable()) {
  490.                     PipeSingleModelGeometry(pCar->car_ID, j, pipe_vertex_count, pipe_array);
  491.                 }
  492.             }
  493.         }
  494.     } else {
  495.         memcpy(&pCar->bounds[1], &storage_bounds, sizeof(br_bounds));
  496.     }
  497. }
  498.  
  499. // IDA: void __cdecl TotallyRepairCar()
  500. void TotallyRepairCar(void) {
  501.     LOG_TRACE("()");
  502.  
  503.     if (!gArrow_mode) {
  504.         TotallyRepairACar(&gProgram_state.current_car);
  505.     }
  506. }
  507.  
  508. // IDA: void __cdecl CheckLastCar()
  509. void CheckLastCar(void) {
  510.     LOG_TRACE("()");
  511.  
  512.     if (gNet_mode == eNet_mode_none && GetCarCount(eVehicle_opponent) != 0 && NumberOfOpponentsLeft() == 0) {
  513.         NewTextHeadupSlot(4, 0, 5000, -4, GetMiscString(kMiscString_EveryOpponentWasted));
  514.         RaceCompleted(eRace_over_opponents);
  515.     }
  516. }
  517.  
  518. // IDA: void __usercall KnackerThisCar(tCar_spec *pCar@<EAX>)
  519. void KnackerThisCar(tCar_spec* pCar) {
  520.     LOG_TRACE("(%p)", pCar);
  521.  
  522.     pCar->knackered = 1;
  523.     QueueWastedMassage(pCar->index);
  524.     CheckLastCar();
  525.     QueueOilSpill(pCar);
  526.     if (gNet_mode == eNet_mode_none) {
  527.         KillGroovadelic(pCar->index);
  528.         KillFunkotronic(pCar->index);
  529.     }
  530. }
  531.  
  532. // IDA: void __usercall SetKnackeredFlag(tCar_spec *pCar@<EAX>)
  533. void SetKnackeredFlag(tCar_spec* pCar) {
  534.     LOG_TRACE("(%p)", pCar);
  535.  
  536.     if (gNet_mode != eNet_mode_client
  537.         && !pCar->knackered
  538.         && (pCar->damage_units[eDamage_engine].damage_level >= 99
  539.             || pCar->damage_units[eDamage_transmission].damage_level >= 99
  540.             || pCar->damage_units[eDamage_driver].damage_level >= 99
  541.             || (pCar->damage_units[eDamage_lf_wheel].damage_level >= 99
  542.                 && pCar->damage_units[eDamage_rf_wheel].damage_level >= 99
  543.                 && pCar->damage_units[eDamage_lr_wheel].damage_level >= 99
  544.                 && pCar->damage_units[eDamage_rr_wheel].damage_level >= 99))) {
  545.         KnackerThisCar(pCar);
  546.         if (gNet_mode == eNet_mode_none) {
  547.             if (IRandomBetween(0, 1)) {
  548.                 if (gNet_mode == eNet_mode_none) {
  549.                     StopCarSmoking(pCar);
  550.                     CreateSmokeColumn(pCar, 0, IRandomBetween(0, 11), 20000);
  551.                 }
  552.             }
  553.             CreateSmokeColumn(pCar, 0, IRandomBetween(0, 11), 180000);
  554.         }
  555.     }
  556. }
  557.  
  558. // IDA: void __usercall DamageUnit2(tCar_spec *pCar@<EAX>, int pUnit_type@<EDX>, int pDamage_amount@<EBX>)
  559. void DamageUnit2(tCar_spec* pCar, int pUnit_type, int pDamage_amount) {
  560.     tDamage_unit* the_damage;
  561.     int last_level;
  562.     LOG_TRACE("(%p, %d, %d)", pCar, pUnit_type, pDamage_amount);
  563.  
  564.     the_damage = &pCar->damage_units[pUnit_type];
  565.     if ((pCar->driver < eDriver_net_human || pUnit_type != eDamage_driver) && pDamage_amount >= 5 && !pCar->invulnerable) {
  566.         last_level = the_damage->damage_level;
  567.         the_damage->damage_level = pDamage_amount + last_level;
  568.         if (the_damage->damage_level >= 99) {
  569.             if (pDamage_amount >= 10) {
  570.                 the_damage->damage_level = 99;
  571.             } else {
  572.                 the_damage->damage_level = last_level;
  573.             }
  574.         }
  575.         if (pCar->driver == eDriver_oppo || gNet_mode != eNet_mode_none) {
  576.             SetKnackeredFlag(pCar);
  577.         } else if ((pCar->damage_units[eDamage_engine].damage_level >= 99 && pCar->damage_units[eDamage_engine].last_level < 99 && pCar->damage_units[eDamage_transmission].last_level < 99)
  578.             || (pCar->damage_units[eDamage_transmission].damage_level >= 99 && pCar->damage_units[eDamage_engine].last_level < 99 && pCar->damage_units[eDamage_transmission].last_level < 99)) {
  579.             QueueOilSpill(pCar);
  580.         }
  581.     }
  582. }
  583.  
  584. // IDA: void __usercall RecordLastDamage(tCar_spec *pCar@<EAX>)
  585. void RecordLastDamage(tCar_spec* pCar) {
  586.     int i;
  587.     LOG_TRACE("(%p)", pCar);
  588.  
  589.     for (i = 0; i < COUNT_OF(pCar->damage_units); i++) {
  590.         pCar->damage_units[i].last_level = pCar->damage_units[i].damage_level;
  591.     }
  592.     pCar->damage_magnitude_accumulator = 0.0;
  593.     pCar->last_impact_location = eImpact_unknown;
  594.     pCar->pre_car_col_mat = pCar->car_master_actor->t.t.mat;
  595.     pCar->pre_car_col_speed = pCar->speed;
  596.     pCar->pre_car_col_knackered = pCar->knackered;
  597.     pCar->pre_car_col_direction = pCar->direction;
  598.     pCar->pre_car_col_velocity = pCar->v;
  599.     pCar->pre_car_col_velocity_car_space = pCar->velocity_car_space;
  600. }
  601.  
  602. // IDA: void __usercall DoDamage(tCar_spec *pCar@<EAX>, tDamage_type pDamage_type@<EDX>, float pMagnitude, float pNastiness)
  603. void DoDamage(tCar_spec* pCar, tDamage_type pDamage_type, float pMagnitude, float pNastiness) {
  604.     LOG_TRACE("(%p, %d, %f, %f)", pCar, pDamage_type, pMagnitude, pNastiness);
  605.  
  606.     if (pCar->driver < eDriver_net_human) {
  607.         DamageUnit2(pCar, pDamage_type, ((gCurrent_race.suggested_rank < 10 ? 0.5f : gCurrent_race.suggested_rank) / 20.0f + 1.0f) * (pNastiness * pMagnitude * 10.0f));
  608.     } else if (gNet_mode != eNet_mode_none) {
  609.         DamageUnit2(pCar, pDamage_type, pNastiness * pMagnitude * 15.0f);
  610.     } else if (PercentageChance(pNastiness * pMagnitude * 1500.0f)) {
  611.         DamageUnit2(pCar, pDamage_type, pNastiness * pMagnitude * 30.0f);
  612.     }
  613. }
  614.  
  615. // IDA: void __usercall CheckPiledriverBonus(tCar_spec *pCar@<EAX>, br_vector3 *pImpact_point@<EDX>, br_vector3 *pEnergy@<EBX>)
  616. void CheckPiledriverBonus(tCar_spec* pCar, br_vector3* pImpact_point, br_vector3* pEnergy) {
  617.     br_actor* child;
  618.     br_vector3 norm_impact;
  619.     br_vector3 norm_child;
  620.     br_vector3 norm_energy;
  621.     //br_scalar dp; // Pierre-Marie Baty -- unused variable
  622.     LOG_TRACE("(%p, %p, %p)", pCar, pImpact_point, pEnergy);
  623.  
  624.     if (pCar->current_car_actor < 0) {
  625.         return;
  626.     }
  627.  
  628.     BrVector3Normalise(&norm_impact, pImpact_point);
  629.     norm_impact.v[1] = 0.f;
  630.     BrVector3Normalise(&norm_energy, pEnergy);
  631.  
  632.     for (child = pCar->car_master_actor->children; child != NULL; child = child->next) {
  633.         if (ActorIsPedestrian(child) && PedestrianActorIsPerson(child) && pCar->speed > 0.001f) {
  634.             BrVector3Normalise(&norm_child, &child->t.t.translate.t);
  635.             norm_child.v[1] = 0.f;
  636.             if (BrVector3Dot(&norm_child, &norm_impact) > 0.8f && BrVector3Dot(&norm_energy, &norm_child) < -.65) {
  637.                 DoFancyHeadup(kFancyHeadupPileDriverBonus);
  638.                 EarnCredits(((GetPedestrianValue(child) / 2 + 12) / 25) * 25);
  639.                 return;
  640.             }
  641.         }
  642.     }
  643. }
  644.  
  645. // IDA: tImpact_location __usercall CalcModifiedLocation@<EAX>(tCar_spec *pCar@<EAX>)
  646. tImpact_location CalcModifiedLocation(tCar_spec* pCar) {
  647.     LOG_TRACE("(%p)", pCar);
  648.  
  649.     if (pCar->last_impact_location != eImpact_left && pCar->last_impact_location != eImpact_right && pCar->last_impact_location != eImpact_top && pCar->last_impact_location != eImpact_bottom) {
  650.         return pCar->last_impact_location;
  651.     }
  652.     if (pCar->last_col_prop_z < 0.25f) {
  653.         return eImpact_front;
  654.     }
  655.     if (pCar->last_col_prop_z > 0.75f) {
  656.         return eImpact_back;
  657.     } else {
  658.         return pCar->last_impact_location;
  659.     }
  660. }
  661.  
  662. // IDA: void __usercall DoPratcamHit(br_vector3 *pHit_vector@<EAX>)
  663. void DoPratcamHit(br_vector3* pHit_vector) {
  664.     int strength_modifier;
  665.     br_scalar strength;
  666.     LOG_TRACE("(%p)", pHit_vector);
  667.  
  668.     strength = BrVector3LengthSquared(pHit_vector);
  669.     if (strength > 0.2f) {
  670.         strength_modifier = 8;
  671.     } else if (strength > 0.015f) {
  672.         strength_modifier = 4;
  673.     } else if (strength >= 0.001f) {
  674.         strength_modifier = 0;
  675.     } else {
  676.         return;
  677.     }
  678.     if (fabsf(pHit_vector->v[2]) >= fabsf(pHit_vector->v[0])) {
  679.         if (pHit_vector->v[2] >= 0.f) {
  680.             PratcamEvent(14 + strength_modifier);
  681.         } else {
  682.             PratcamEvent(13 + strength_modifier);
  683.         }
  684.     } else {
  685.         if (pHit_vector->v[0] >= 0.f) {
  686.             PratcamEvent(15 + strength_modifier);
  687.         } else {
  688.             PratcamEvent(16 + strength_modifier);
  689.         }
  690.     }
  691. }
  692.  
  693. // IDA: void __usercall DamageSystems(tCar_spec *pCar@<EAX>, br_vector3 *pImpact_point@<EDX>, br_vector3 *pEnergy_vector@<EBX>, int pWas_hitting_a_car@<ECX>)
  694. void DamageSystems(tCar_spec* pCar, br_vector3* pImpact_point, br_vector3* pEnergy_vector, int pWas_hitting_a_car) {
  695.     int i;
  696.     int j;
  697.     int result;
  698.     br_bounds crushed_car_bounds;
  699.     float proportion_x;
  700.     float proportion_y;
  701.     float proportion_z;
  702.     float energy_magnitude;
  703.     float pure_energy_magnitude;
  704.     br_scalar x;
  705.     br_scalar y;
  706.     br_scalar z;
  707.     br_scalar x1;
  708.     br_scalar x2;
  709.     br_scalar y1;
  710.     br_scalar y2;
  711.     br_scalar z1;
  712.     br_scalar z2;
  713.     //br_scalar distance; // Pierre-Marie Baty -- unused variable
  714.     tImpact_location impact_location;
  715.     tDamage_program* the_program;
  716.     tDamage_clause* the_clause;
  717.     tDamage_condition* the_condition;
  718.     tDamage_effect* the_effect;
  719.     //tImpact_location modified_location; // Pierre-Marie Baty -- unused variable
  720.     LOG_TRACE("(%p, %p, %p, %d)", pCar, pImpact_point, pEnergy_vector, pWas_hitting_a_car);
  721.  
  722. #if defined(DETHRACE_FIX_BUGS)
  723.     proportion_x = 0;
  724.     proportion_y = 0;
  725.     proportion_z = 0;
  726. #endif
  727.  
  728.     pure_energy_magnitude = BrVector3Length(pEnergy_vector);
  729.     if (pure_energy_magnitude == 0.0f && !pWas_hitting_a_car) {
  730.         return;
  731.     }
  732.  
  733.     energy_magnitude = pCar->car_model_actors[pCar->principal_car_actor].crush_data.softness_factor * pure_energy_magnitude / 0.7f;
  734.     BrVector3InvScale(&crushed_car_bounds.min, &pCar->bounds[1].min, WORLD_SCALE);
  735.     BrVector3InvScale(&crushed_car_bounds.max, &pCar->bounds[1].max, WORLD_SCALE);
  736.  
  737.     x1 = pImpact_point->v[0] - crushed_car_bounds.min.v[0];
  738.     x2 = crushed_car_bounds.max.v[0] - pImpact_point->v[0];
  739.     if (x1 >= x2) {
  740.         x = x2;
  741.     } else {
  742.         x = x1;
  743.     }
  744.     y1 = pImpact_point->v[1] - crushed_car_bounds.min.v[1];
  745.     y2 = crushed_car_bounds.max.v[1] - pImpact_point->v[1];
  746.     if (y1 >= y2) {
  747.         y = y2;
  748.     } else {
  749.         y = y1;
  750.     }
  751.     z1 = pImpact_point->v[2] - crushed_car_bounds.min.v[2];
  752.     z2 = crushed_car_bounds.max.v[2] - pImpact_point->v[2];
  753.     if (z1 >= z2) {
  754.         z = z2;
  755.     } else {
  756.         z = z1;
  757.     }
  758.     if (z > x || z > y) {
  759.         if (x > y || x > z) {
  760.             impact_location = y1 < y2 ? eImpact_bottom : eImpact_top;
  761.             proportion_z = z1 / (crushed_car_bounds.max.v[2] - crushed_car_bounds.min.v[2]);
  762.             proportion_x = x1 / (crushed_car_bounds.max.v[0] - crushed_car_bounds.min.v[0]);
  763.         } else {
  764.             impact_location = x1 >= x2 ? eImpact_right : eImpact_left;
  765.             proportion_z = z1 / (crushed_car_bounds.max.v[2] - crushed_car_bounds.min.v[2]);
  766.             proportion_y = y1 / (crushed_car_bounds.max.v[1] - crushed_car_bounds.min.v[1]);
  767.         }
  768.     } else {
  769.         impact_location = z1 >= z2 ? eImpact_back : eImpact_front;
  770.         proportion_x = x1 / (crushed_car_bounds.max.v[0] - crushed_car_bounds.min.v[0]);
  771.         proportion_y = y1 / (crushed_car_bounds.max.v[1] - crushed_car_bounds.min.v[1]);
  772.     }
  773.     if (pWas_hitting_a_car && pCar->last_impact_location == eImpact_unknown) {
  774.         pCar->last_impact_location = impact_location;
  775.         pCar->last_col_prop_x = proportion_x;
  776.         pCar->last_col_prop_y = proportion_y;
  777.         pCar->last_col_prop_z = proportion_z;
  778.     }
  779.  
  780.     if (energy_magnitude != 0.0f && !pCar->invulnerable) {
  781.         if (!pWas_hitting_a_car && impact_location == eImpact_bottom) {
  782.             energy_magnitude = energy_magnitude / 2.0f;
  783.         }
  784.  
  785.         the_program = &pCar->damage_programs[impact_location];
  786.         the_clause = the_program->clauses;
  787.         for (i = 0; i < the_program->clause_count; i++) {
  788.             result = 1;
  789.             the_condition = the_clause->conditions;
  790.             for (j = 0; j < the_clause->condition_count; j++) {
  791.                 switch (the_condition->axis_comp) {
  792.                 case eAxis_x:
  793.                     if (the_condition->condition_operator == eCondition_greater_than) {
  794.                         if (the_condition->comparitor >= proportion_x) {
  795.                             result = 0;
  796.                         }
  797.                     } else if (the_condition->comparitor <= proportion_x) {
  798.                         result = 0;
  799.                     }
  800.                     break;
  801.  
  802.                 case eAxis_y:
  803.                     if (the_condition->condition_operator == eCondition_greater_than) {
  804.                         if (the_condition->comparitor >= proportion_y) {
  805.                             result = 0;
  806.                         }
  807.                     } else if (the_condition->comparitor <= proportion_y) {
  808.                         result = 0;
  809.                     }
  810.                     break;
  811.  
  812.                 case eAxis_z:
  813.                     if (the_condition->condition_operator == eCondition_greater_than) {
  814.                         if (the_condition->comparitor >= proportion_z) {
  815.                             result = 0;
  816.                         }
  817.                     } else if (the_condition->comparitor <= proportion_z) {
  818.                         result = 0;
  819.                     }
  820.                     break;
  821.                 }
  822.  
  823.                 if (!result) {
  824.                     break;
  825.                 }
  826.                 the_condition++;
  827.             }
  828.             if (result) {
  829.                 for (j = 0; j < the_clause->effect_count; j++) {
  830.                     the_effect = &the_clause->effects[j];
  831.                     DoDamage(pCar, the_effect->type, energy_magnitude, the_effect->weakness_factor);
  832.                 }
  833.             }
  834.             the_clause++;
  835.         }
  836.         if (pCar->driver == eDriver_local_human) {
  837.             switch (impact_location) {
  838.             case eImpact_top:
  839.             case eImpact_bottom:
  840.                 NewScreenWobble(
  841.                     FRandomBetween(energy_magnitude * 5.0f, energy_magnitude * 20.0f),
  842.                     FRandomBetween(energy_magnitude * 30.0f, energy_magnitude * 60.0f),
  843.                     FRandomBetween(1.0f / energy_magnitude, 5.0f / energy_magnitude));
  844.                 break;
  845.             case eImpact_left:
  846.                 NewScreenWobble(
  847.                     FRandomBetween(energy_magnitude * 50.0f, energy_magnitude * 100.0f),
  848.                     FRandomBetween(energy_magnitude * 5.0f, energy_magnitude * 20.0f),
  849.                     FRandomBetween(4.0f / energy_magnitude, 7.0 / energy_magnitude));
  850.                 break;
  851.             case eImpact_right:
  852.                 NewScreenWobble(
  853.                     -FRandomBetween(energy_magnitude * 50.0f, energy_magnitude * 100.0f),
  854.                     FRandomBetween(energy_magnitude * 5.0f, energy_magnitude * 20.0f),
  855.                     FRandomBetween(4.0f / energy_magnitude, 7.0f / energy_magnitude));
  856.  
  857.                 break;
  858.             case eImpact_front:
  859.                 NewScreenWobble(
  860.                     FRandomBetween(energy_magnitude * 5.0f, energy_magnitude * 20.0f),
  861.                     FRandomBetween(energy_magnitude * 50.0f, energy_magnitude * 150.0f),
  862.                     FRandomBetween(7.0f / energy_magnitude, 25.0f / energy_magnitude));
  863.                 break;
  864.             case eImpact_back:
  865.                 NewScreenWobble(
  866.                     FRandomBetween(energy_magnitude * 5.0f, energy_magnitude * 20.0f),
  867.                     FRandomBetween(-energy_magnitude * 50.0f, energy_magnitude * 150.0f),
  868.                     FRandomBetween(7.0f / energy_magnitude, 25.0f / energy_magnitude));
  869.                 break;
  870.             default:
  871.                 break;
  872.             }
  873.             CheckPiledriverBonus(pCar, pImpact_point, pEnergy_vector);
  874.         }
  875.     }
  876. }
  877.  
  878. // IDA: tImpact_location __usercall GetDirection@<EAX>(br_vector3 *pVelocity@<EAX>)
  879. tImpact_location GetDirection(br_vector3* pVelocity) {
  880.     br_scalar mag_x;
  881.     br_scalar mag_y;
  882.     br_scalar mag_z;
  883.     LOG_TRACE("(%p)", pVelocity);
  884.  
  885.     mag_x = fabsf(pVelocity->v[0]);
  886.     mag_y = fabsf(pVelocity->v[1]);
  887.     mag_z = fabsf(pVelocity->v[2]);
  888.     if (mag_y >= mag_x || mag_z >= mag_x) {
  889.         if (mag_y <= mag_x || mag_z >= mag_y) {
  890.             if (pVelocity->v[2] >= 0.0f) {
  891.                 return eImpact_back;
  892.             } else {
  893.                 return eImpact_front;
  894.             }
  895.         } else {
  896.             return pVelocity->v[1] < 0.0;
  897.         }
  898.     } else if (pVelocity->v[0] >= 0.0) {
  899.         return eImpact_right;
  900.     } else {
  901.         return eImpact_left;
  902.     }
  903. }
  904.  
  905. // IDA: void __usercall SetSmokeLastDamageLevel(tCar_spec *pCar@<EAX>)
  906. void SetSmokeLastDamageLevel(tCar_spec* pCar) {
  907.     int i;
  908.     LOG_TRACE("(%p)", pCar);
  909.  
  910.     for (i = 0; i < COUNT_OF(pCar->damage_units); i++) {
  911.         pCar->damage_units[i].smoke_last_level = pCar->damage_units[i].damage_level;
  912.     }
  913. }
  914.  
  915. // IDA: void __usercall SortOutSmoke(tCar_spec *pCar@<EAX>)
  916. void SortOutSmoke(tCar_spec* pCar) {
  917.     int i;
  918.     int colour;
  919.     int old_colour;
  920.     int step;
  921.     //int pass; // Pierre-Marie Baty -- unused variable
  922.     //int repeat; // Pierre-Marie Baty -- unused variable
  923.     LOG_TRACE("(%p)", pCar);
  924.  
  925.     if (!pCar || pCar->driver <= eDriver_non_car) {
  926.         return;
  927.     }
  928.     for (i = 0; i < COUNT_OF(pCar->damage_units); i++) {
  929.         if (pCar->damage_units[i].damage_level != pCar->damage_units[i].smoke_last_level) {
  930.             step = gSmoke_damage_step[i];
  931.             if (step) {
  932.                 if (pCar->damage_units[i].damage_level > pCar->damage_units[i].smoke_last_level) {
  933.                     old_colour = (99 - pCar->damage_units[i].smoke_last_level) / step;
  934.                     colour = (99 - pCar->damage_units[i].damage_level) / step;
  935.                     if (old_colour != colour && colour <= 2) {
  936.                         ConditionalSmokeColumn(pCar, i, colour);
  937.                     }
  938.                 }
  939.             }
  940.         }
  941.     }
  942.     SetSmokeLastDamageLevel(pCar);
  943. }
  944.  
  945. // IDA: void __usercall StealCar(tCar_spec *pCar@<EAX>)
  946. void StealCar(tCar_spec* pCar) {
  947.     LOG_TRACE("(%p)", pCar);
  948.  
  949.     pCar->has_been_stolen = 1;
  950.     gProgram_state.cars_available[gProgram_state.number_of_cars] = pCar->index;
  951.     gProgram_state.number_of_cars++;
  952.     gOpponents[pCar->index].dead = 1;
  953. }
  954.  
  955. // IDA: int __usercall DoCrashEarnings@<EAX>(tCar_spec *pCar1@<EAX>, tCar_spec *pCar2@<EDX>)
  956. int DoCrashEarnings(tCar_spec* pCar1, tCar_spec* pCar2) {
  957.     tCar_spec* culprit;
  958.     tCar_spec* victim;
  959.     int i;
  960.     int net_loop;
  961.     int head_on;
  962.     int bonus_level;
  963.     int credits;
  964.     int impact_in_moving_direction_1;
  965.     int impact_in_moving_direction_2;
  966.     int car_off_ground_1;
  967.     int car_off_ground_2;
  968.     int total_units_of_damage;
  969.     int inherited_damage;
  970.     int dam_acc_1;
  971.     int dam_acc_2;
  972.     int car_1_culpable;
  973.     int car_2_culpable;
  974.     int mutual_culpability;
  975.     tU32 the_time;
  976.     tU32 time;
  977.     float credits_squared;
  978.     static tU32 last_earn_time;
  979.     //char s[256]; // Pierre-Marie Baty -- unused variable
  980.     tImpact_location modified_location_1;
  981.     tImpact_location modified_location_2;
  982.     tImpact_location car_direction_1;
  983.     tImpact_location car_direction_2;
  984.     br_scalar car_1_height;
  985.     br_scalar car_2_height;
  986.     br_scalar dp;
  987.     br_vector3 car_1_pos;
  988.     br_vector3 car_2_pos;
  989.     br_vector3 car_1_offset;
  990.     br_vector3 car_2_offset;
  991.     tNet_message* message;
  992.     LOG_TRACE("(%p, %p)", pCar1, pCar2);
  993.  
  994.     culprit = 0;
  995.     victim = 0;
  996.     head_on = 0;
  997.     bonus_level = 1;
  998.     car_1_culpable = 0;
  999.     car_2_culpable = 0;
  1000.     mutual_culpability = 0;
  1001.     the_time = PDGetTotalTime();
  1002.     inherited_damage = 0;
  1003. #if defined(DETHRACE_FIX_BUGS)
  1004.     total_units_of_damage = 0;
  1005. #endif
  1006.     if (pCar1->driver <= eDriver_non_car) {
  1007.         dam_acc_1 = 0;
  1008.     } else {
  1009.         dam_acc_1 = pCar1->damage_magnitude_accumulator;
  1010.     }
  1011.  
  1012.     dam_acc_2 = 0;
  1013.     if (pCar2 != NULL) {
  1014.         if (pCar2->driver <= eDriver_non_car) {
  1015.             dam_acc_2 = 0;
  1016.         } else {
  1017.             dam_acc_2 = pCar2->damage_magnitude_accumulator != 0;
  1018.         }
  1019.     }
  1020.  
  1021.     if (pCar1->driver <= eDriver_non_car) {
  1022.         if (pCar2 == NULL || pCar2->driver <= eDriver_non_car) {
  1023.             return 0;
  1024.         }
  1025.         pCar1 = pCar2;
  1026.         pCar2 = NULL;
  1027.     }
  1028.     if (pCar2 != NULL && pCar2->driver <= eDriver_non_car) {
  1029.         pCar2 = NULL;
  1030.     }
  1031.  
  1032.     if (pCar1->pre_car_col_knackered || (pCar2 && pCar2->pre_car_col_knackered) || (pCar2 && pCar2->damage_magnitude_accumulator <= 0.00005f && pCar1->damage_magnitude_accumulator <= 0.00005f)) {
  1033.         return dam_acc_1 || (pCar2 && dam_acc_2);
  1034.     }
  1035.  
  1036.     modified_location_1 = CalcModifiedLocation(pCar1);
  1037.     car_direction_1 = GetDirection(&pCar1->pre_car_col_velocity_car_space);
  1038.     impact_in_moving_direction_1 = car_direction_1 == modified_location_1;
  1039.     if (pCar2 != NULL) {
  1040.         modified_location_2 = CalcModifiedLocation(pCar2);
  1041.         car_direction_2 = GetDirection(&pCar2->pre_car_col_velocity_car_space);
  1042.         impact_in_moving_direction_2 = car_direction_2 == modified_location_2;
  1043.     }
  1044.     if (pCar1->driver >= eDriver_net_human && pCar2) {
  1045.         if (impact_in_moving_direction_1 && (pCar1->driver < eDriver_net_human || (pCar1->pre_car_col_velocity_car_space.v[2] != 0.0 && (pCar1->pre_car_col_velocity_car_space.v[2] > 0.0) != (pCar1->gear > 0) && (pCar1->keys.acc != 0 || pCar1->joystick.acc > 0x8000)))) {
  1046.             pCar2->time_last_hit = the_time;
  1047.             pCar2->last_hit_by = pCar1;
  1048.         }
  1049.     } else if (pCar2 && pCar2->driver >= eDriver_net_human && impact_in_moving_direction_2 && (pCar2->driver < eDriver_net_human || (pCar2->pre_car_col_velocity_car_space.v[2] != 0.0f && (pCar2->pre_car_col_velocity_car_space.v[2] > 0.0f) != (pCar2->gear > 0) && (pCar2->keys.acc != 0 || pCar2->joystick.acc > 0x8000)))) {
  1050.         pCar1->time_last_hit = the_time;
  1051.         pCar1->last_hit_by = pCar2;
  1052.     }
  1053.     if (pCar2) {
  1054.         if (impact_in_moving_direction_1
  1055.             && pCar1->pre_car_col_speed * 5.0f > pCar2->pre_car_col_speed
  1056.             && pCar1->pre_car_col_speed > 0.0005f
  1057.             && (pCar1->driver < eDriver_net_human
  1058.                 || (pCar1->pre_car_col_velocity_car_space.v[2] != 0.0f
  1059.                     && (pCar1->pre_car_col_velocity_car_space.v[2] > 0.0f) != (pCar1->gear > 0)
  1060.                     && (pCar1->keys.acc != 0 || pCar1->joystick.acc > 0x8000)))) {
  1061.             car_1_culpable = 1;
  1062.         }
  1063.         if (impact_in_moving_direction_2
  1064.             && pCar2->pre_car_col_speed * 5.0f > pCar1->pre_car_col_speed
  1065.             && pCar2->pre_car_col_speed > 0.0005f
  1066.             && (pCar2->driver < eDriver_net_human
  1067.                 || (pCar2->pre_car_col_velocity_car_space.v[2] != 0.0f
  1068.                     && (pCar2->pre_car_col_velocity_car_space.v[2] > 0.0f) != (pCar2->gear > 0)
  1069.                     && (pCar2->keys.acc != 0 || pCar2->joystick.acc > 0x8000)))) {
  1070.             car_2_culpable = 1;
  1071.         }
  1072.         if (gNet_mode && car_1_culpable && car_2_culpable) {
  1073.             mutual_culpability = 1;
  1074.         } else {
  1075.             if (car_2_culpable && pCar2->driver == eDriver_local_human) {
  1076.                 car_1_culpable = 0;
  1077.             }
  1078.             if (car_1_culpable) {
  1079.                 culprit = pCar1;
  1080.                 victim = pCar2;
  1081.                 dp = BrVector3Dot(&pCar1->pre_car_col_direction, &pCar2->pre_car_col_direction);
  1082.                 if (modified_location_1 == eImpact_front && modified_location_2 == eImpact_front && pCar1->pre_car_col_speed > 0.001f && pCar2->pre_car_col_speed > 0.001f && dp < -0.7f) {
  1083.                     head_on = 1;
  1084.                     bonus_level = 2;
  1085.                 } else {
  1086.                     bonus_level = 1;
  1087.                 }
  1088.             } else if (car_2_culpable) {
  1089.                 culprit = pCar2;
  1090.                 victim = pCar1;
  1091.                 dp = BrVector3Dot(&pCar1->pre_car_col_direction, &pCar2->pre_car_col_direction);
  1092.                 if (modified_location_1 == eImpact_front && modified_location_2 == eImpact_front && pCar1->pre_car_col_speed > 0.001f && pCar2->pre_car_col_speed > 0.001f && dp < -0.7f) {
  1093.                     head_on = 1;
  1094.                     bonus_level = 2;
  1095.                 } else {
  1096.                     bonus_level = 1;
  1097.                 }
  1098.             }
  1099.         }
  1100.     } else {
  1101.         if (the_time - pCar1->time_last_hit >= 3000) {
  1102.             return 1;
  1103.         }
  1104.         culprit = pCar1->last_hit_by;
  1105.         victim = pCar1;
  1106.         bonus_level = 1;
  1107.         inherited_damage = 1;
  1108.     }
  1109.     if (!mutual_culpability && (!victim || culprit->driver < eDriver_net_human)) {
  1110.         if (pCar2 && pCar2->last_culprit == pCar1 && the_time - pCar2->time_last_victim < 750) {
  1111.             inherited_damage = 1;
  1112.             culprit = pCar1;
  1113.             victim = pCar2;
  1114.         } else if (pCar2 && pCar1->last_culprit == pCar2 && the_time - pCar1->time_last_victim < 750) {
  1115.             inherited_damage = 1;
  1116.             culprit = pCar2;
  1117.             victim = pCar1;
  1118.         } else if (!pCar2 && the_time - pCar1->time_last_victim < 750) {
  1119.             inherited_damage = 1;
  1120.             culprit = pCar1->last_culprit;
  1121.             victim = pCar1;
  1122.         }
  1123.     }
  1124.     if (culprit && victim) {
  1125.         RecordOpponentTwattageOccurrence(culprit, victim);
  1126.         total_units_of_damage = 0;
  1127.         for (i = 0; i < COUNT_OF(victim->damage_units); i++) {
  1128.             if (victim->damage_units[i].damage_level > victim->damage_units[i].last_level) {
  1129.                 victim->damage_units[i].damage_level = (victim->damage_units[i].damage_level - victim->damage_units[i].last_level) * 2.0f + victim->damage_units[i].last_level;
  1130.                 if (victim->damage_units[i].damage_level >= 99) {
  1131.                     victim->damage_units[i].damage_level = 99;
  1132.                 }
  1133.                 total_units_of_damage += victim->damage_units[i].damage_level - victim->damage_units[i].last_level;
  1134.             }
  1135.             if (culprit->damage_units[i].damage_level > culprit->damage_units[i].last_level) {
  1136.                 culprit->damage_units[i].damage_level = (culprit->damage_units[i].damage_level - culprit->damage_units[i].last_level) * 0.1f + (double)culprit->damage_units[i].last_level;
  1137.                 if (culprit->damage_units[i].damage_level < 0) {
  1138.                     culprit->damage_units[i].damage_level = 0;
  1139.                 }
  1140.             }
  1141.         }
  1142.     }
  1143.     // TODO: tidy this up
  1144.     for (net_loop = 0; 2 - (mutual_culpability == 0) > net_loop; net_loop++) {
  1145.         if (mutual_culpability) {
  1146.             if (net_loop) {
  1147.                 culprit = pCar1;
  1148.                 victim = pCar2;
  1149.             } else {
  1150.                 culprit = pCar2;
  1151.                 victim = pCar1;
  1152.             }
  1153.             total_units_of_damage = 0;
  1154.             for (i = 0; i < COUNT_OF(victim->damage_units); i++) {
  1155.                 if (victim->damage_units[i].damage_level > victim->damage_units[i].last_level) {
  1156.                     total_units_of_damage += victim->damage_units[i].damage_level - victim->damage_units[i].last_level;
  1157.                 }
  1158.             }
  1159.         }
  1160.         if (culprit && (culprit->driver == eDriver_local_human || gNet_mode) && victim) {
  1161.             SetKnackeredFlag(victim);
  1162.             if (victim->knackered && !victim->pre_car_col_knackered) {
  1163.                 victim->pre_car_col_knackered = 1;
  1164.                 credits_squared = sqr(0.7f / victim->car_model_actors[victim->principal_car_actor].crush_data.softness_factor) * gWasted_creds[gProgram_state.skill_level] + 50.0f;
  1165.                 credits = 100 * (int)(credits_squared / 100.0f);
  1166.                 if (gNet_mode) {
  1167.                     message = NetBuildMessage(0x18u, 0);
  1168.                     message->contents.data.wasted.victim = NetPlayerFromCar(victim)->ID;
  1169.                     if (NetPlayerFromCar(culprit)) {
  1170.                         message->contents.data.wasted.culprit = NetPlayerFromCar(culprit)->ID;
  1171.                     } else {
  1172.                         message->contents.data.wasted.culprit = -2;
  1173.                     }
  1174.                     NetGuaranteedSendMessageToEverybody(gCurrent_net_game, message, NULL);
  1175.                     NetEarnCredits(NetPlayerFromCar(culprit), credits);
  1176.                 } else {
  1177.                     PratcamEvent(32);
  1178.                     DoFancyHeadup(kFancyHeadupYouWastedEm);
  1179.                     credits_squared = sqr(0.7f / victim->car_model_actors[victim->principal_car_actor].crush_data.softness_factor) * gWasted_creds[gProgram_state.skill_level] + 50.0f;
  1180.                     credits = 100 * (int)(credits_squared / 100.0);
  1181.                     AwardTime(gWasted_time[gProgram_state.skill_level]);
  1182.                     EarnCredits(credits);
  1183.                     if (victim->can_be_stolen && !gOpponents[victim->index].dead
  1184.                         // strength_rating is between 1 and 5
  1185.                         && ((PercentageChance(50) && gProgram_state.rank <= gSteal_ranks[gOpponents[victim->index].strength_rating - 1]) || victim->index == BIGAPC_OPPONENT_INDEX)) {
  1186.                         StealCar(victim);
  1187.                     }
  1188.                 }
  1189.             }
  1190.             victim->time_last_hit = the_time;
  1191.             victim->last_hit_by = culprit;
  1192.             if (!inherited_damage) {
  1193.                 victim->time_last_victim = the_time;
  1194.                 victim->last_culprit = culprit;
  1195.             }
  1196.             if (victim && (fabs(victim->omega.v[0]) > 4.0f || fabs(victim->omega.v[1]) > 6.0f || fabs(victim->omega.v[2]) > 4.0f)) {
  1197.                 bonus_level *= 2;
  1198.             }
  1199.             if (pCar1->number_of_wheels_on_ground) {
  1200.                 car_off_ground_1 = 0;
  1201.             } else {
  1202.                 BrVector3InvScale(&car_1_pos, &pCar1->car_master_actor->t.t.translate.t, WORLD_SCALE);
  1203.                 BrMatrix34ApplyV(&car_1_offset, &pCar1->car_model_actors[pCar1->principal_car_actor].actor->t.t.translate.t, &pCar1->car_master_actor->t.t.mat);
  1204.                 BrVector3Accumulate(&car_1_pos, &car_1_offset);
  1205.                 car_1_pos.v[1] += 0.15f;
  1206.                 car_1_height = FindYVerticallyBelow2(&car_1_pos);
  1207.                 car_off_ground_1 = car_1_height > -100.0f
  1208.                     && pCar1->car_model_actors[pCar1->principal_car_actor].actor->t.t.translate.t.v[1] * 4.0f <= car_1_pos.v[1] - car_1_height - 0.15f;
  1209.             }
  1210.             if (!pCar2 || pCar2->number_of_wheels_on_ground) {
  1211.                 car_off_ground_2 = 0;
  1212.             } else {
  1213.                 BrVector3InvScale(&car_2_pos, &pCar2->car_master_actor->t.t.translate.t, WORLD_SCALE);
  1214.                 BrMatrix34ApplyV(&car_2_offset, &pCar2->car_model_actors[pCar2->principal_car_actor].actor->t.t.translate.t, &pCar2->car_master_actor->t.t.mat);
  1215.                 BrVector3Accumulate(&car_2_pos, &car_2_offset);
  1216.                 car_2_pos.v[1] += 0.15f;
  1217.                 car_2_height = FindYVerticallyBelow2(&car_2_pos);
  1218.                 car_off_ground_2 = car_2_height > -100.0f
  1219.                     && pCar2->car_model_actors[pCar2->principal_car_actor].actor->t.t.translate.t.v[1] * 4.0f <= car_2_pos.v[1] - car_2_height - 0.15f;
  1220.             }
  1221.             if (car_off_ground_1) {
  1222.                 bonus_level *= 2;
  1223.             }
  1224.             if (car_off_ground_2) {
  1225.                 bonus_level *= 2;
  1226.             }
  1227.             total_units_of_damage = 0.7f / victim->car_model_actors[victim->principal_car_actor].crush_data.softness_factor * total_units_of_damage;
  1228.             if (!victim->has_been_stolen) {
  1229.                 credits = 100 * (int)((gCar_cred_value[gProgram_state.skill_level] * MIN(bonus_level, 8) * total_units_of_damage + 50.0f) / 100.0f);
  1230.                 if (credits || victim->knackered) {
  1231.                     if (!victim->knackered) {
  1232.                         if (gNet_mode) {
  1233.                             NetEarnCredits(NetPlayerFromCar(culprit), MIN(credits, 2000));
  1234.                         } else {
  1235.                             EarnCredits(MIN(credits, 2000));
  1236.                         }
  1237.                         last_earn_time = the_time;
  1238.                         if (gNet_mode == eNet_mode_none) {
  1239.                             time = 5 * (int)((total_units_of_damage * gCar_time_value[gProgram_state.skill_level] + 2.5f) / 5.0f);
  1240.                             AwardTime(MIN(time, 90));
  1241.                             if (pCar2) {
  1242.                                 if (head_on) {
  1243.                                     DoFancyHeadup(kFancyHeadupHeadOnBonus);
  1244.                                 } else if (bonus_level <= 2) {
  1245.                                     if (bonus_level > 1) {
  1246.                                         DoFancyHeadup(kFancyHeadupExtraStyleBonus);
  1247.                                     }
  1248.                                 } else {
  1249.                                     DoFancyHeadup(kFancyHeadupBonusForArtisticImpression);
  1250.                                 }
  1251.                             }
  1252.                         }
  1253.                     }
  1254.                     for (i = 0; i < COUNT_OF(victim->damage_units); i++) {
  1255.                         victim->damage_units[i].last_level = victim->damage_units[i].damage_level;
  1256.                     }
  1257.                 }
  1258.             }
  1259.         } else {
  1260.             pCar1->time_last_hit = 0;
  1261.             if (pCar2) {
  1262.                 pCar2->time_last_hit = 0;
  1263.             }
  1264.         }
  1265.     }
  1266.     pCar1->damage_magnitude_accumulator = 0.0f;
  1267.     if (pCar2) {
  1268.         pCar2->damage_magnitude_accumulator = 0.0f;
  1269.     }
  1270.     return 1;
  1271. }
  1272.  
  1273. // IDA: void __usercall DoWheelDamage(tU32 pFrame_period@<EAX>)
  1274. void DoWheelDamage(tU32 pFrame_period) {
  1275.     int i;
  1276.     int j;
  1277.     int damage;
  1278.     tCar_spec* car;
  1279.     br_scalar y_amount;
  1280.     br_scalar z_amount;
  1281.     br_scalar wheel_circum;
  1282.     br_scalar old_offset;
  1283.     br_vector3 temp_vector;
  1284.     br_vector3 wonky_vector;
  1285.     static int kev_index[4];
  1286.     LOG_TRACE("(%d)", pFrame_period);
  1287.  
  1288.     if (gAction_replay_mode && ReplayIsPaused()) {
  1289.         return;
  1290.     }
  1291.  
  1292.     for (i = 0; i < gNum_active_cars; i++) {
  1293.         car = gActive_car_list[i];
  1294.         for (j = 0; j < COUNT_OF(car->wheel_dam_offset); j++) {
  1295.             if (car->wheel_actors[j] == NULL) {
  1296.                 continue;
  1297.             }
  1298.             old_offset = car->wheel_dam_offset[j];
  1299.             damage = car->damage_units[j + 8].damage_level;
  1300.             if (damage <= 30 || gRace_finished) {
  1301.                 car->wheel_dam_offset[j] = 0.0f;
  1302.                 continue;
  1303.             }
  1304.             if (PointOutOfSight(&car->pos, 32.0f)) {
  1305.                 break;
  1306.             }
  1307.             y_amount = (damage - 30) * gWobble_spam_y[damage & 7];
  1308.             z_amount = (damage - 30) * gWobble_spam_z[damage & 7];
  1309.             BrMatrix34PreRotateY(&car->wheel_actors[j]->t.t.mat, BrDegreeToAngle(y_amount < 0 ? y_amount + 360.f : y_amount));
  1310.             BrMatrix34PreRotateZ(&car->wheel_actors[j]->t.t.mat, BrDegreeToAngle(z_amount < 0 ? z_amount + 360.f : z_amount));
  1311.             if (j < 2 && car->wheel_actors[j + 4] != NULL) {
  1312.                 BrMatrix34PreRotateY(&car->wheel_actors[j + 4]->t.t.mat, BrDegreeToAngle(y_amount < 0 ? y_amount + 360.f : y_amount));
  1313.                 BrMatrix34PreRotateZ(&car->wheel_actors[j + 4]->t.t.mat, BrDegreeToAngle(z_amount < 0 ? z_amount + 360.f : z_amount));
  1314.             }
  1315.             switch (j) {
  1316.             case 0:
  1317.                 if (car->driven_wheels_spin_ref_1 < 0) {
  1318.                     wheel_circum = car->non_driven_wheels_circum;
  1319.                 } else {
  1320.                     wheel_circum = car->driven_wheels_circum;
  1321.                 }
  1322.                 break;
  1323.             case 1:
  1324.                 if (car->driven_wheels_spin_ref_2 < 0) {
  1325.                     wheel_circum = car->non_driven_wheels_circum;
  1326.                 } else {
  1327.                     wheel_circum = car->driven_wheels_circum;
  1328.                 }
  1329.                 break;
  1330.             case 2:
  1331.                 if (car->driven_wheels_spin_ref_3 < 0) {
  1332.                     wheel_circum = car->non_driven_wheels_circum;
  1333.                 } else {
  1334.                     wheel_circum = car->driven_wheels_circum;
  1335.                 }
  1336.                 break;
  1337.             case 3:
  1338.                 if (car->driven_wheels_spin_ref_4 < 0) {
  1339.                     wheel_circum = car->non_driven_wheels_circum;
  1340.                 } else {
  1341.                     wheel_circum = car->driven_wheels_circum;
  1342.                 }
  1343.                 break;
  1344.             default:
  1345.                 TELL_ME_IF_WE_PASS_THIS_WAY();
  1346.                 break;
  1347.             }
  1348.             if (gNet_mode == eNet_mode_none || car->driver == eDriver_local_human) {
  1349.                 BrVector3Set(&temp_vector, wheel_circum * gWheel_circ_to_width, 0.f, 0.f);
  1350.                 BrMatrix34ApplyV(&wonky_vector, &temp_vector, &car->wheel_actors[j]->t.t.mat);
  1351.                 car->wheel_dam_offset[j] = fabsf(wonky_vector.v[1]);
  1352.             }
  1353.         }
  1354.     }
  1355. }
  1356.  
  1357. // IDA: void __usercall CrashEarnings(tCar_spec *pCar1@<EAX>, tCar_spec *pCar2@<EDX>)
  1358. void CrashEarnings(tCar_spec* pCar1, tCar_spec* pCar2) {
  1359.     LOG_TRACE("(%p, %p)", pCar1, pCar2);
  1360.  
  1361.     if (DoCrashEarnings(pCar1, pCar2)) {
  1362.         SortOutSmoke(pCar1);
  1363.         SortOutSmoke(pCar2);
  1364.     }
  1365. }
  1366.