#include <libultraship.h>
#include <macros.h>
#include <defines.h>
#include <stubs.h>
#include "course_metadata.h"
#include <align_asset_macro.h>

#include "code_80005FD0.h"
#include "math_util.h"
#include "code_800029B0.h"
#include "racing/memory.h"
#include "waypoints.h"
#include "camera.h"
#include "actors.h"
#include "actors_extended.h"
#include "render_player.h"
#include "player_controller.h"
#include "update_objects.h"
#include "collision.h"
#include <actor_types.h>
#include "vehicles.h"
#include "render_objects.h"
#include "code_80057C60.h"
#include "bomb_kart.h"
#include "courses/all_course_data.h"
#include <assets/common_data.h>
#include <common_structs.h>
#include "main.h"
#include "menus.h"
#include "menu_items.h"
#include "audio/external.h"
#include "ending/podium_ceremony_actors.h"
#include "spawn_players.h"
#include "sounds.h"
#include "data/path_spawn_metadata.h"
#include <assets/kalimari_desert_data.h>
#include <assets/frappe_snowland_data.h>
#include <assets/dks_jungle_parkway_data.h>
#include <stdio.h>

#include "port/Game.h"
#include "engine/courses/Course.h"

s32 unk_code_80005FD0_pad[24];
Collision D_80162E70;
s16 D_80162EB0; // Possibly a float.
s16 D_80162EB2; // possibly [3]

CPUBehaviour* gCoursesCPUBehaviour[NUM_COURSES - 1];

// more padding?
s32 D_80162F08[2];

s16 D_80162F10[30];
s16 D_80162F50[30];

// padding, seemingly?
s32 D_80162F90[4];

Vec3f gOffsetPosition;
Vec3f D_80162FB0;
Vec3f D_80162FC0;
s16 gTrainSmokeTimer;
s16 sSomeNearestPathPoint;
s16 D_80162FD0;
f32 gCourseCompletionPercentByRank[NUM_PLAYERS];
/*
The size of many of these arrays is weird. Based on their usage, they would only need
to be 8 entries long (enough for each player). But some are 10 or even 12 long.
Its plausible that this is just some decompilation artifact?
Or maybe at some point in development they had plans for more players?
*/
s16 D_80162FF8[12];
s16 D_80163010[12];
f32 cpu_TargetSpeed[10];
s16 gPreviousAngleSteering[12];
f32 gTrackPositionFactor[10];
f32 D_80163090[10];
bool gIsPlayerInCurve[10];
u16 gCurrentNearestPathPoint;
s16 gIsPlayerNewPathPoint;
s16 D_801630E8[10];
s16 gFerrySmokeTimer;
s32 D_80163100[10];
s32 D_80163128[10];
s32 D_80163150[10];
f32 gPreviousPlayerAiOffsetX[10];
f32 gPreviousPlayerAiOffsetZ[10];
s16 sVehicleSoundRenderCounter;
s32 D_801631CC;
TrackPathPoint* gCurrentTrackLeftPath;
TrackPathPoint* gCurrentTrackRightPath;
s16* gCurrentTrackSectionTypesPath;
s16* gCurrentPathPointExpectedRotationPath;
u16 D_801631E0[12];
u16 D_801631F8[10];
f32 gCurrentCpuTargetSpeed;
f32 gPreviousCpuTargetSpeed[10];
s32 D_80163238;
u16 D_80163240[12];
u16 gWrongDirectionCounter[12];
u16 gIsPlayerWrongDirection[12];
s32 gPreviousLapProgressScore[10];
CPUBehaviour* sCurrentCPUBehaviour;
u16 gCurrentCPUBehaviourId[12];
u16 gPreviousCPUBehaviourId[12];
u16 cpu_BehaviourState[12];
s16 sPlayerAngle[12];
u16 gPlayersTrackSectionId[12];
u16 D_80163330[10];
u16 D_80163344[2];
u16 D_80163348[2];
u16 D_8016334C[8]; // Increased from 2 to 8 to prevent array overflow.
u16 gSpeedCPUBehaviour[12];
s32 D_80163368[4];
s32 gIncrementUpdatePlayer;
s32 D_8016337C;
s16 gCurrentPlayerLookAhead[12];
s16 D_80163398[12];
s16 D_801633B0[12];
s16 D_801633C8[12];
s16 D_801633E0[12];
s16 D_801633F8[12];
s16 D_80163410[4];
f32 D_80163418[4];
f32 D_80163428[4];
f32 D_80163438[4];
s32 gPlayerPathIndex;
f32 gPathStartZ;
f32 gPreviousPlayerZ[10];
s16 gBestRankedHumanPlayer;
s16 gIsInExtra;
s16 D_8016347C;
s16 D_8016347E;
s32 D_80163480;
s32 D_80163484;
s32 D_80163488;
s16 D_8016348C;
s16 cpu_enteringPathIntersection[12];
s16 cpu_exitingPathIntersection[12];
s16 D_801634C0[12];
s16 bStopAICrossing[10];
s16 D_801634EC;
s32 D_801634F0;
s32 D_801634F4;
TrackPositionFactorInstruction gPlayerTrackPositionFactorInstruction[10];
Path2D* gVehicle2DPathPoint;
s32 gVehicle2DPathLength;
TrainStuff gTrainList[NUM_TRAINS];
u16 isCrossingTriggeredByIndex[NUM_CROSSINGS];
u16 sCrossingActiveTimer[NUM_CROSSINGS];
PaddleBoatStuff gPaddleBoats[NUM_PADDLE_BOATS];
VehicleStuff gBoxTruckList[NUM_RACE_BOX_TRUCKS];
VehicleStuff gSchoolBusList[NUM_RACE_SCHOOL_BUSES];
VehicleStuff gTankerTruckList[NUM_RACE_TANKER_TRUCKS];
VehicleStuff gCarList[NUM_RACE_CARS];
s32 D_80163DD8[4];
BombKart gBombKarts[NUM_BOMB_KARTS_MAX];
Collision gBombKartCollision[NUM_BOMB_KARTS_MAX];
struct unexpiredActors gUnexpiredActorsList[8];
CpuItemStrategyData cpu_ItemStrategy[8];
s16 D_80164358;
s16 D_8016435A;
s16 D_8016435C;
s16 gGPCurrentRacePlayerIdByRank[12]; // D_80164360
s16 D_80164378[12];
s32 gLapCountByPlayerId[10];          // D_80164390
s32 gGPCurrentRaceRankByPlayerId[10]; // D_801643B8
s32 gPreviousGPCurrentRaceRankByPlayerId[10];
s32 gGPCurrentRaceRankByPlayerIdDup[10];
u16 gSelectedPathCount;
u16 gNearestPathPointByPlayerId[12];
s32 gNumPathPointsTraversed[10];
s16 gGetPlayerByCharacterId[10];
s32 D_8016448C;
TrackPathPoint* gCurrentTrackPath;
f32 D_80164498[4];
f32 gLapCompletionPercentByPlayerId[10];    // D_801644A8
f32 gCourseCompletionPercentByPlayerId[10]; // D_801644D0
s16 bInMultiPathSection[12];
f32 gPlayerPathY[10];
s16 D_80164538[12];
TrackPathPoint* gTrackPaths[4];
TrackPathPoint* gTrackLeftPaths[4];
TrackPathPoint* gTrackRightPaths[4];
s16* gTrackSectionTypes[4];
s16* gPathExpectedRotation[4];
s16* gTrackConsecutiveCurveCounts[4];
u16 gPathIndexByPlayerId[12]; // D_801645B0
u16 gPathCountByPathIndex[4]; // D_801645C8
s32 D_801645D0[4];
s16* gCurrentTrackConsecutiveCurveCountsPath;
s32 D_801645E8[4];
f32 D_801645F8[4];
s32 D_80164608[4];
f32 D_80164618[4];
s32 D_80164628[4];
f32 D_80164638[4];
f32 D_80164648[4];
f32 D_80164658[4];
s16 gNearestPathPointByCameraId[4];
s16 D_80164670[4];
s16 D_80164678[4];
s16 D_80164680[4];
f32 D_80164688[4];
f32 D_80164698;
f32 D_8016469C;
f32 D_801646A0;
// start padding
s32 D_801646A4;
s32 D_801646A8;
s32 D_801646AC;
s32 D_801646B0;
s32 D_801646B4;
s32 D_801646B8;
s32 D_801646BC;
// end padding
s16 D_801646C0[4];
u32 D_801646C8;
u16 D_801646CC;
UnkStruct_46D0 D_801646D0[4];

// Strings, presented by google translate!
// Note that these are EUC-JP encoded, see:
// https://en.wikipedia.org/wiki/Extended_Unix_Code#EUC-JP

// Force sort immediately after goal
char* D_800EB710 = "ゴール直後の強制ソート\n";
// Forced sort immediately after one goal in 2PGP
char* D_800EB728 = "2PGPで片方がゴール直後の強制ソート\n";
// rank calculation error
char* D_800EB74C = "順位計算エラー！！ (num %d) (rank %d) (e_rank %d)\n";
// Bypass switching error!!!(num %d org_bipas %d bipas %d)
char* D_800EB780 = "バイパス切り替え エラー!!!(num %d  org_bipas %d  bipas %d)\n";
char* D_800EB7BC = "(%d) rap %3d  rate_count_F %10.2f  rap_count_F %10.2f  area %5d \n";
// Enter the maze! enemy %d (%d --> %d)
char* D_800EB800 = "迷路に突入！ enemy %d (%d --> %d)\n";
// Out of the maze! enemy %d (%d --> %d)
char* D_800EB824 = "迷路から出た！ enemy %d (%d --> %d)\n";
char* D_800EB84C = "enemy voice set (%d  slip_flag %x  weapon %x)\n";
// Spin Voice! ! (%d , name %d)
char* D_800EB87C = "スピンヴォイス！！(%d , name %d)\n";
// Damage voice! ! (%d, name %d)
char* D_800EB8A0 = "ダメージヴォイス！！(%d, name %d)\n";
char* D_800EB8C4 = "===== ENEMY DRIVE SUB (%d) =====\n";
// omission
char* D_800EB8E8 = "ENEMY END(手抜き)\n\n";
char* D_800EB8FC = "ENEMY END(手抜き)\n\n";
char* D_800EB910 = "(1)enemy stick angle over!! (%d)\n";
char* D_800EB934 = "ENEMY END\n\n";
char* D_800EB940 = "(2)enemy stick angle over!! (%d)\n";
char* D_800EB964 = "ENEMY END\n\n";
// AREA ERR!!! (group not registered at current centerline %d) %d
char* D_800EB970 = "AREA ERR!!! (現在のセンターライン %d に未登録のグループです) %d\n";
// AREA ERR!!! (Unregistered group) %d
char* D_800EB9B4 = "AREA ERR!!! (未登録のグループです) %d\n";
// get_oga_area_sub_BP() ... Area not found! (b_num = %d)
char* D_800EB9DC = "get_oga_area_sub_BP() ... エリアが見つからないッス！ (b_num = %d)\n";
// Status: (%d, %d, %d)
char* D_800EBA20 = "  状況: (%d, %d, %d) \n";
char* D_800EBA38 = "<%d> (%d, %d, %d) [%d] lng %f\n";
// Wario Stadium Jump failed! ! ! (area %d, y %7.2f)
char* D_800EBA58 = "ワリオスタジアム  ジャンプ失敗！！！ (area %d, y %7.2f)\n";
// I fell in the water! ! Forced to centerline (num %d: area %d ) (%d,%d,%d)
char* D_800EBA94 = "水に落ちた！！  センターラインに強制移動しました (num %d: area %d ) (%d,%d,%d)\n";
// Course match! ! (Slacking: with bump) Forced move to center line (num %d: area %d ==>%d) (group %d) (%d,%d,%d)
char* D_800EBAE4 = "こーすあうと！！（手抜き中:バンプ有り）  センターラインに強制移動しました (num %d: area %d ==>%d) "
                   "(group %d) (%d,%d,%d)\n";
// Course match! ! (Sitting corners: no bump) Forced move to center line (num %d: area %d ==>%d) (group %d) (%d,%d,%d)
char* D_800EBB60 = "こーすあうと！！（手抜き中:バンプ無し）  センターラインに強制移動しました (num %d: area %d ==>%d) "
                   "(group %d) (%d,%d,%d)\n";
// Course match! ! ! Recalculated area (num %d: area %d ==>%d)
char* D_800EBBDC = "こーすあうと！！！    エリアを再計算しました (num %d: area %d ==>%d)\n";
// Direct BOM(%d) (%7.2f, %7.2f, %7.2f)
char* D_800EBC24 = "直接指定のBOM(%d) (%7.2f, %7.2f, %7.2f) \n";
char* D_800EBC50 = "BOM HIT CHECK\n";
char* D_800EBC60 = "BOM HIT !!!!! (%d)\n";
// BOM standby
char* D_800EBC74 = "BOM待機\n";
char* D_800EBC80 = "RESULT BOM area(%d)\n";
// BOM dropped.
char* D_800EBC98 = "BOM が 落ちました。\n";
// Tortoise fire pillar SET failed (TABLE IS FULL)
char* D_800EBCB0 = "カメ用火柱 SET 失敗 (TABLE IS FULL)\n";
// Red turtle fire pillar set error! (category %d)
char* D_800EBCD8 = "赤ガメ火柱セットエラー！ (category %d)\n";
// Blue turtle fire pillar set error! (category %d)
char* D_800EBD00 = "青ガメ火柱セットエラー！ (category %d)\n";
// Thorn Turtle Fire Pillar Set Error! (category %d)
char* D_800EBD28 = "トゲガメ火柱セットエラー！ (category %d)\n";
// Turtle Fire Pillar Initialization! !
char* D_800EBD54 = "カメ火柱初期化！！\n";
// Center line initialization
char* D_800EBD68 = "センターライン初期化\n";
char* D_800EBD80 = "MAP NUMBER %d\n";
char* D_800EBD90 = "center_EX ptr      = %x %x (%x)\n";
char* D_800EBDB4 = "\n";
char* D_800EBDB8 = "center_BP[%d] ptr         = %x %x (%x)\n";
char* D_800EBDE0 = "side_point_L_BP[%d] ptr   = %x %x (%x)\n";
char* D_800EBE08 = "side_point_R_BP[%d] ptr   = %x %x (%x)\n";
char* D_800EBE30 = "curve_BP[%d] ptr          = %x %x (%x)\n";
char* D_800EBE58 = "angle_BP[%d] ptr          = %x %x (%x)\n";
char* D_800EBE80 = "short_cut_data_BP[%d] ptr = %x %x (%x)\n";
char* D_800EBEA8 = "\n";
// Ogawa total memory used = %d
char* D_800EBEAC = "小川の使用メモリー合計 = %d\n";
// Enemy initialization
char* D_800EBECC = "敵初期化\n";
// End of enemy initialization
char* D_800EBED8 = "敵初期化終了\n";
// Bypass CENTER LINE Split start
char* D_800EBEE8 = "バイパス CENTER LINE 分割開始\n";
// Read centerline from ROM (map:%d)
char* D_800EBF08 = "センターラインをROMから読みます (map:%d)\n";
char* D_800EBF34 = "ROM center (BP%d) line adr. = %x (%x)\n";
// Calculate centerline (map:%d)
char* D_800EBF5C = "センターラインを計算します (map:%d)\n";
char* D_800EBF84 = "center (BP%d) line adr. = %x (%x)\n";
char* D_800EBFA8 = "BP center_point_number : %d\n";
// Centerline data error! !
char* D_800EBFC8 = "センターライン データ エラー！！\n";
// Bypass CENTER LINE split end (%d -> %d number)
char* D_800EBFEC = "バイパス CENTER LINE 分割終了 (%d -> %d 個)\n";
// No center line. (map: %d)
char* D_800EC01C = "センターラインが ありません。(map:%d)\n";
// side point calculation (bypass %d)
char* D_800EC044 = "サイドポイント計算 (バイパス %d)\n";
// Curve data calculation (bypass %d)
char* D_800EC068 = "カーブデータ計算 (バイパス %d)\n";
// No center line. (map: %d)
char* D_800EC088 = "センターラインが ありません。(map:%d)\n";
// Angle data calculation (bypass %d)
char* D_800EC0B0 = "アングルデータ計算 (バイパス %d) \n";
// No center line. (map: %d)
char* D_800EC0D4 = "センターラインが ありません。(map:%d)\n";
// Shortcut data calculation (bypass %d)
char* D_800EC0FC = "ショートカットデータ計算 (バイパス %d)\n";
char* D_800EC124 = "extern POINT rom_center_KT%d_BP%d[] = {\n";
char* D_800EC150 = "\t{%d,%d,%d,%d},\n";
char* D_800EC164 = "\t0x8000,0x8000,0x8000,0\n};\n\n";
char* D_800EC184 = "area read from ROM (%d)\n";
// Normal jump! ! ! (%d)
char* D_800EC1A0 = "ノーマルジャンプ！！！(%d)\n";
// Turbo on! ! ! (%d)
char* D_800EC1BC = "ターボオン！！！(%d)\n";
// No cutting corners! ! ! (%d)
char* D_800EC1D4 = "手抜き禁止！！！(%d)\n";
// Action start data error! (num %d, act %d)
char* D_800EC1EC = "アクション開始データエラー！(num %d, act %d)\n";
// Action end data error! (num %d, act %d, old_act_num %d)
char* D_800EC21C = "アクション終了データエラー！(num %d,  act %d,  old_act_num %d)\n";
char* D_800EC25C = "SL : center_point_number : %d\n";
// SL: CENTER LINE split start
char* D_800EC27C = "SL: CENTER LINE 分割開始\n";
// SL: CENTER LINE split ended (%d -> %d indivual)
char* D_800EC298 = "SL: CENTER LINE 分割終了 (%d -> %d 個)\n";
char* D_800EC2C0 = "SHIP : center_point_number : %d\n";
// SHIP: CENTER LINE split start
char* D_800EC2E4 = "SHIP: CENTER LINE 分割開始\n";
// SHIP: CENTER LINE split ended (%d -> %d indivual)
char* D_800EC300 = "SHIP: CENTER LINE 分割終了 (%d -> %d 個)\n";
// General-purpose OBJ character initialization
char* D_800EC32C = "汎用OBJキャラ初期化\n";
// SL OBJ settings
char* D_800EC344 = "SL OBJ設定\n";
// SHIP OBJ settings
char* D_800EC350 = "SHIP OBJ設定\n";
// Track OBJ settings
char* D_800EC360 = "トラックOBJ設定\n";
// Bus OBJ setting
char* D_800EC374 = "バスOBJ設定\n";
// Tank OBJ setting
char* D_800EC384 = "タンクOBJ設定\n";
// RV OBJ settings
char* D_800EC394 = "RV OBJ設定\n";
// Generic OBJ character initialization completed
char* D_800EC3A0 = "汎用OBJキャラ初期化終了\n";
// horn (num %d, permit %d, %d)
char* D_800EC3BC = "クラクション (num %d, permit %d, %d)\n";
char* D_800EC3E4 = "OGA CAMERA INIT (%d)\n";
char* D_800EC3FC = "OGA CAMERA INIT END\n";
// High speed camera ERR !!! (ncx = %f)
char* D_800EC414 = "高速カメラ ERR !!! (ncx = %f)\n";
// High speed camera ERR !!! (ncz = %f)
char* D_800EC434 = "高速カメラ ERR !!! (ncz = %f)\n";
// High speed camera ERR !!! (ecx = %f)
char* D_800EC454 = "高速カメラ ERR !!! (ecx = %f)\n";
// High speed camera ERR !!! (ecz = %f)
char* D_800EC474 = "高速カメラ ERR !!! (ecz = %f)\n";
char* D_800EC494 = "OGA DRIVERS POINT CAMERA MODE \n";
char* D_800EC4B4 = "OGA WINNER CAMERA MODE \n";
char* D_800EC4D0 = "OGA TIMEATTACK QUICK CAMERA INIT \n";
char* D_800EC4F4 = "OGA BATTLE CAMERA INIT win(%d)\n";
char* D_800EC514 = "GOAL! <<rank 1>> camera %d  rank %d\n";
char* D_800EC53C = "GOAL! <<rank 2,3,4>> camera %d  rank %d\n";
char* D_800EC568 = "GOAL! <<rank 5,6,7,8>> camera %d  rank %d\n";
// Camera and cart collided! ! !
char* D_800EC594 = "カメラとカートが衝突しました！！！  (%d)\n";
char* D_800EC5C0 = "<<< ITEM OBJ NUMBER ERR !! >>> item %d  obj_num %d \n";
// <<< BANANA SET HOUSE >>> obj_num %d zure %f
char* D_800EC5F8 = "<<< BANANA SET 失敗 >>> obj_num %d   zure %f \n";
// BANANA Caught in owner check. (num %d)
char* D_800EC628 = "BANANA 所有者チェックに引っ掛かりました。(num %d)\n";
// Reason: EXISTOBJ
char* D_800EC65C = "理由: EXISTOBJ \n";
// Reason: category
char* D_800EC670 = "理由: category \n";
// Reason: sparam
char* D_800EC684 = "理由: sparam \n";
// Reason: num
char* D_800EC694 = "理由: num \n";
char* D_800EC6A0 = "BANANA HOLD (num %d  time %d   hold_time %d)\n";
// Installation Caught in BANANA owner check. (num %d)
char* D_800EC6D0 = "設置 BANANA 所有者チェックに引っ掛かりました。(num %d)\n";
// Reason: EXISTOBJ
char* D_800EC708 = "理由: EXISTOBJ \n";
// Reason: category
char* D_800EC71C = "理由: category \n";
// Reason: sparam
char* D_800EC730 = "理由: sparam \n";
// Reason: num
char* D_800EC740 = "理由: num \n";
// I put BANANA. (num %d)
char* D_800EC74C = "BANANA 置きました。 (num %d)\n";
// <<< BANANA NAGE SET failed >>> obj_num %d
char* D_800EC76C = "<<< BANANA NAGE SET 失敗 >>> obj_num %d \n";
// BANANA NAGE MOVE Caught in owner check. (num %d)
char* D_800EC798 = "BANANA NAGE MOVE 所有者チェックに引っ掛かりました。(num %d)\n";
// Reason: EXISTOBJ
char* D_800EC7D8 = "理由: EXISTOBJ \n";
// Reason: category
char* D_800EC7EC = "理由: category \n";
// Reason: sparam
char* D_800EC800 = "理由: sparam \n";
// Reason: num
char* D_800EC810 = "理由: num \n";
char* D_800EC81C = "BANANA NAGE END 所有者チェックに引っ掛かりました。(num %d)\n";
// Reason: EXISTOBJ
char* D_800EC858 = "理由: EXISTOBJ \n";
// Reason: category
char* D_800EC86C = "理由: category \n";
// Reason: sparam
char* D_800EC880 = "理由: sparam \n";
// Reason: num
char* D_800EC890 = "理由: num \n";
char* D_800EC89C = "G_SHELL HOLD (num %d  time %d   hold_time %d)\n";
// <<< G_SHELL SET failed >>> obj_num %d
char* D_800EC8CC = "<<< G_SHELL SET 失敗 >>> obj_num %d \n";
// <<< G_SHELL SET failed >>> object_count %d
char* D_800EC8F4 = "<<< G_SHELL SET 失敗 >>> object_count %d \n";
// G_SHELL Caught in owner check. (num %d)
char* D_800EC920 = "G_SHELL 所有者チェックに引っ掛かりました。(num %d)\n";
// Reason: EXISTOBJ
char* D_800EC954 = "理由: EXISTOBJ \n";
// Reason: category
char* D_800EC968 = "理由: category \n";
// Reason: sparam
char* D_800EC97C = "理由: sparam \n";
// Reason: num
char* D_800EC98C = "理由: num \n";
// Just before launch G_SHELL Caught in owner check. (num %d)
char* D_800EC998 = "発射直前 G_SHELL 所有者チェックに引っ掛かりました。(num %d)\n";
// Reason: EXISTOBJ
char* D_800EC9D8 = "理由: EXISTOBJ \n";
// Reason: category
char* D_800EC9EC = "理由: category \n";
// Reason: sparam
char* D_800ECA00 = "理由: sparam \n";
// Reason: num
char* D_800ECA10 = "理由: num \n";
// G_SHELL firing (num %d)
char* D_800ECA1C = "G_SHELL 発射 (num %d)\n";
char* D_800ECA34 = "R_SHELL HOLD (num %d  time %d   hold_time %d  obj_num %d)\n";
// <<< R_SHELL SET failed >>> obj_num %d
char* D_800ECA70 = "<<< R_SHELL SET 失敗 >>> obj_num %d \n";
// <<< R_SHELL SET failed >>> object_count %d
char* D_800ECA98 = "<<< R_SHELL SET 失敗 >>> object_count %d \n";
// R_SHELL Caught in owner check. (num %d)
char* D_800ECAC4 = "R_SHELL 所有者チェックに引っ掛かりました。(num %d)\n";
// Reason: EXISTOBJ
char* D_800ECAF8 = "理由: EXISTOBJ \n";
// Reason: category
char* D_800ECB0C = "理由: category \n";
// Reason: sparam
char* D_800ECB20 = "理由: sparam \n";
// Reason: num
char* D_800ECB30 = "理由: num \n";
char* D_800ECB3C = "R_SHELL SHOOT (num %d  time %d   hold_time %d  obj_num %d)\n";
// Just before launch R_SHELL Caught in owner check. (num %d)
char* D_800ECB78 = "発射直前 R_SHELL 所有者チェックに引っ掛かりました。(num %d)\n";
// Reason: EXISTOBJ
char* D_800ECBB8 = "理由: EXISTOBJ \n";
// Reason: category
char* D_800ECBCC = "理由: category \n";
// Reason: sparam
char* D_800ECBE0 = "理由: sparam \n";
// Reason: num
char* D_800ECBF0 = "理由: num \n";
// R_SHELL firing (num %d)
char* D_800ECBFC = "R_SHELL 発射 (num %d)\n";
char* D_800ECC14 = "S_BANANA HOLD (num %d  time %d   hold_time %d)\n";
// <<< SUPER_BANANA SET failed >>> obj_num %d
char* D_800ECC44 = "<<< SUPER_BANANA SET 失敗 >>> obj_num %d \n";
// <<< SUPER_BANANA SET failed >>> object_count %d
char* D_800ECC70 = "<<< SUPER_BANANA SET 失敗 >>> object_count %d \n";
// S_BANANA Caught in owner check. (num %d)
char* D_800ECCA0 = "S_BANANA 所有者チェックに引っ掛かりました。(num %d)\n";
// Reason: category
char* D_800ECCD8 = "理由: category \n";
// Reason: sparam
char* D_800ECCEC = "理由: sparam \n";
// Reason: sb_ok
char* D_800ECCFC = "理由: sb_ok \n";
char* D_800ECD0C = "S_BANANA RELEASE (num %d  time %d )\n";
// <<< FAKE IBOX SET failed >>> obj_num %d
char* D_800ECD34 = "<<< FAKE IBOX SET 失敗 >>> obj_num %d \n";
// IBOX Caught in owner check. (num %d)
char* D_800ECD5C = "IBOX 所有者チェックに引っ掛かりました。(num %d)\n";
// Reason: EXISTOBJ
char* D_800ECD90 = "理由: EXISTOBJ \n";
// Reason: category
char* D_800ECDA4 = "理由: category \n";
// Reason: sparam
char* D_800ECDB8 = "理由: sparam \n";
// Reason: num
char* D_800ECDC8 = "理由: num \n";
char* D_800ECDD4 = "FBOX HOLD (num %d  time %d   hold_time %d)\n";
// Installation IBOX owner check failed. (num %d)
char* D_800ECE00 = "設置 IBOX 所有者チェックに引っ掛かりました。(num %d)\n";
// Reason: EXISTOBJ
char* D_800ECE38 = "理由: EXISTOBJ \n";
// Reason: category
char* D_800ECE4C = "理由: category \n";
// Reason: sparam
char* D_800ECE60 = "理由: sparam \n";
// Reason: num
char* D_800ECE70 = "理由: num \n";
// Ray START (%d)
char* D_800ECE7C = "雷START (%d)\n";
// Ray END (%d)
char* D_800ECE8C = "雷END (%d)\n";
// ---------- Initialization of commendation table
char* D_800ECE98 = "---------- 表彰台初期化\n";
// map_number = %d -> 20 Rewriting.
char* D_800ECEB4 = "map_number = %d - > 20 書き換え中。\n";
// OGA Recognition move begins
char* D_800ECEDC = "OGA 表彰 move 開始\n";
// I called the display of the 4th place person.
char* D_800ECEF0 = "４位の人の表示をコールしました。\n";
// Arrive at the podium
char* D_800ECF14 = "表彰台に到着\n";
// Everyone gather!
char* D_800ECF24 = "全員集合！\n";
// Arrive on the road
char* D_800ECF30 = "道路に到着\n";
// 4th place finished
char* D_800ECF3C = "４位の人終了\n";
// OGA commendation move end
char* D_800ECF4C = "OGA 表彰 move 終了\n";
char* D_800ECF60 = "OGAWA DEBUG DRAW\n";

s16 get_angle_between_path(Vec3f arg0, Vec3f arg1) {
    s16 temp_ret;
    s16 phi_v1;

    temp_ret = get_angle_between_two_vectors(arg0, arg1);
    phi_v1 = temp_ret;
    if (gIsMirrorMode != 0) {
        phi_v1 = -temp_ret;
    }
    return phi_v1;
}

s32 is_collide_with_vehicle(f32 vehicleX, f32 vehicleZ, f32 vehicleVelocityX, f32 vehicleVelocityZ, f32 distanceX,
                            f32 distanceY, f32 playerX, f32 playerZ) {
    f32 velocity;
    f32 temp_f18;

    velocity = sqrtf((vehicleVelocityX * vehicleVelocityX) + (vehicleVelocityZ * vehicleVelocityZ));
    if (velocity < 0.01f) {
        return 0;
    }
    temp_f18 =
        ((vehicleVelocityX / velocity) * (playerX - vehicleX)) + ((vehicleVelocityZ / velocity) * (playerZ - vehicleZ));
    if ((-distanceX < temp_f18) && (temp_f18 < distanceX)) {
        temp_f18 = ((vehicleVelocityZ / velocity) * (playerX - vehicleX)) +
                   (-(vehicleVelocityX / velocity) * (playerZ - vehicleZ));
        if ((-distanceY < temp_f18) && (temp_f18 < distanceY)) {
            return 1;
        }
    }
    return 0;
}

void adjust_position_by_angle(Vec3f newPos, Vec3f oldPos, s16 orientationY) {
    f32 x_dist;
    f32 z_dist;
    f32 temp1;
    f32 temp2;
    f32 sine;
    f32 cosine;

    if (gIsMirrorMode != 0) {
        orientationY = -orientationY;
    }
    x_dist = newPos[0] - oldPos[0];
    z_dist = newPos[2] - oldPos[2];
    sine = sins(orientationY);
    cosine = coss(orientationY);
    temp1 = ((x_dist * cosine) + (z_dist * sine));
    temp2 = ((z_dist * cosine) - (x_dist * sine));
    newPos[0] = oldPos[0] + temp1;
    newPos[2] = oldPos[2] + temp2;
}

s32 set_vehicle_render_distance_flags(Vec3f vehiclePos, f32 renderDistance, s32 flags) {
    Camera* camera;
    Player* player;
    f32 x;
    f32 z;
    f32 playerX;
    f32 playerZ;
    s32 i;
    s32 flag;
    s8 numScreens;

    x = vehiclePos[0];
    z = vehiclePos[2];
    switch (gActiveScreenMode) {
        case SCREEN_MODE_1P:
            numScreens = 1;
            break;
        case SCREEN_MODE_2P_SPLITSCREEN_HORIZONTAL:
        case SCREEN_MODE_2P_SPLITSCREEN_VERTICAL:
            numScreens = 2;
            break;
        case SCREEN_MODE_3P_4P_SPLITSCREEN:
            numScreens = gPlayerCount;
            break;
        default:
            numScreens = 1;
            break;
    }
    flag = flags;
    if (!gDemoMode) {
        player = gPlayerOne;
        // Checks distance from each player.
        for (i = 0; i < gPlayerCount; i++, player++) {
            if (((player->type & PLAYER_HUMAN) != 0) && ((player->type & PLAYER_CPU) == 0)) {
                playerX = player->pos[0];
                playerZ = player->pos[2];

                // Is player within render distance
                if (((playerX - renderDistance) < x) && ((playerX + renderDistance) > x) &&
                    ((playerZ - renderDistance) < z) && ((playerZ + renderDistance) > z)) {
                    // Sets the render flag to on for each player.
                    flag |= (RENDER_VEHICLE << i);
                } else {
                    // Sets the render flag to off for each player.
                    flag &= ~(RENDER_VEHICLE << i);
                }
            }
        }
    } else { // Demo cinematic uses the camera to check render distance
        camera = camera1;
        for (i = 0; i < numScreens; i++, camera++) {
            playerX = camera->pos[0];
            playerZ = camera->pos[2];
            if (((playerX - renderDistance) < x) && (x < (playerX + renderDistance)) &&
                ((playerZ - renderDistance) < z) && (z < (playerZ + renderDistance))) {
                flag |= (RENDER_VEHICLE << i);
            } else {
                flag &= ~(RENDER_VEHICLE << i);
            }
        }
    }
    if (CVarGetInteger("gNoCulling", 0) == 1) {
        flag |= (RENDER_VEHICLE << PLAYER_ONE) | (RENDER_VEHICLE << PLAYER_TWO) | (RENDER_VEHICLE << PLAYER_THREE) |
                (RENDER_VEHICLE << PLAYER_FOUR);
    }
    return flag;
}

void detect_wrong_player_direction(s32 playerId, Player* player) {
    s16 playerAngle;
    s16 rotationDifference;
    s16 pathPointAngle;
    s16 pathIndex;
    u32 pathPoint;

    pathIndex = (s16) gPathIndexByPlayerId[playerId];
    pathPoint = gNearestPathPointByPlayerId[playerId];

    playerAngle = (s16) ((s16) player->rotation[1] / DEGREES_CONVERSION_FACTOR);
    pathPointAngle = (s16) ((s16) gPathExpectedRotation[pathIndex][pathPoint] / DEGREES_CONVERSION_FACTOR);

    rotationDifference = playerAngle - pathPointAngle;

    if (rotationDifference < 0) {
        rotationDifference = -rotationDifference;
    }

    if ((gNumPathPointsTraversed[playerId] < gPreviousLapProgressScore[playerId]) &&
        (rotationDifference >= SEVERE_WRONG_DIRECTION_MIN) && (rotationDifference < SEVERE_WRONG_DIRECTION_MAX)) {
        gWrongDirectionCounter[playerId]++;
        if ((gWrongDirectionCounter[playerId]) >= WRONG_DIRECTION_FRAMES_LIMIT) {
            gIsPlayerWrongDirection[playerId] = 1;
            gWrongDirectionCounter[playerId] = WRONG_DIRECTION_FRAMES_LIMIT;
            gPlayers[playerId].effects |= REVERSE_EFFECT;
        }
    } else if ((rotationDifference < SEVERE_CORRECT_DIRECTION_MIN) ||
               (rotationDifference >= SEVERE_CORRECT_DIRECTION_MAX)) {
        gIsPlayerWrongDirection[playerId] = 0;
        gWrongDirectionCounter[playerId] = 0;
        gPlayers[playerId].effects &= ~REVERSE_EFFECT;
    }
    gPreviousLapProgressScore[playerId] = gNumPathPointsTraversed[playerId];
}

void set_places(void) {
    s32 temp_s2;
    f32 temp_f0;
    s32 sp80[8];
    s32 a_really_cool_variable_name;
    UNUSED s32 pad;
    s32 var_t4;
    s32 playerId;
    s32 temp_a0;
    s32 var_t1_3;

    switch (gModeSelection) {
        case BATTLE:
        default:
            return; // HEY! returns, not breaks
        case GRAND_PRIX:
        case TIME_TRIALS:
            var_t4 = NUM_PLAYERS;
            break;
        case VERSUS:
            var_t4 = gPlayerCount;
            break;
    }

    if (D_8016348C == 0) {
        for (playerId = 0; playerId < var_t4; playerId++) {
            temp_a0 = gGPCurrentRacePlayerIdByRank[playerId];
            sp80[playerId] = temp_a0;
            gCourseCompletionPercentByRank[playerId] = gCourseCompletionPercentByPlayerId[temp_a0];
        }
    } else {
        for (playerId = 0; playerId < var_t4; playerId++) {
            temp_a0 = gGPCurrentRacePlayerIdByRank[playerId];
            sp80[playerId] = temp_a0;
            gCourseCompletionPercentByRank[playerId] = -gTimePlayerLastTouchedFinishLine[temp_a0];
        }
    }

    for (playerId = 0; playerId < var_t4 - 1; playerId++) {
        if ((gPlayers[gGPCurrentRacePlayerIdByRank[playerId]].type & 0x800)) {
            continue;
        }

        for (var_t1_3 = playerId + 1; var_t1_3 < var_t4; var_t1_3++) {
            if (gCourseCompletionPercentByRank[playerId] < gCourseCompletionPercentByRank[var_t1_3]) {
                if (!(gPlayers[gGPCurrentRacePlayerIdByRank[var_t1_3]].type & 0x800)) {
                    temp_s2 = sp80[playerId];
                    sp80[playerId] = sp80[var_t1_3];
                    sp80[var_t1_3] = temp_s2;
                    temp_f0 = gCourseCompletionPercentByRank[playerId];
                    gCourseCompletionPercentByRank[playerId] = gCourseCompletionPercentByRank[var_t1_3];
                    gCourseCompletionPercentByRank[var_t1_3] = temp_f0;
                }
            }
        }
    }

    for (playerId = 0; playerId < NUM_PLAYERS; playerId++) {
        gPreviousGPCurrentRaceRankByPlayerId[playerId] = gGPCurrentRaceRankByPlayerId[playerId];
    }

    for (playerId = 0; playerId < var_t4; playerId++) {
        gGPCurrentRacePlayerIdByRank[playerId] = sp80[playerId];
        gGPCurrentRaceRankByPlayerId[sp80[playerId]] = playerId;
    }

    for (playerId = 0; playerId < var_t4; playerId++) {
        a_really_cool_variable_name = D_80164378[playerId];
        sp80[playerId] = a_really_cool_variable_name;
        gCourseCompletionPercentByRank[playerId] = gCourseCompletionPercentByPlayerId[a_really_cool_variable_name];
    }

    for (playerId = 0; playerId < var_t4 - 1; playerId++) {
        for (var_t1_3 = playerId + 1; var_t1_3 < var_t4; var_t1_3++) {
            if (gCourseCompletionPercentByRank[playerId] < gCourseCompletionPercentByRank[var_t1_3]) {
                temp_s2 = sp80[playerId];
                sp80[playerId] = sp80[var_t1_3];
                sp80[var_t1_3] = temp_s2;
                temp_f0 = gCourseCompletionPercentByRank[playerId];
                gCourseCompletionPercentByRank[playerId] = gCourseCompletionPercentByRank[var_t1_3];
                gCourseCompletionPercentByRank[var_t1_3] = temp_f0;
            }
        }
    }

    for (playerId = 0; playerId < var_t4; playerId++) {
        gGPCurrentRaceRankByPlayerIdDup[sp80[playerId]] = playerId;
        D_80164378[playerId] = sp80[playerId];
    }
}

void update_player_rankings(void) {
    f32 temp_f0;
    UNUSED s32 pad;
    s32 sp50[8];
    s32 temp_a0;
    s32 temp_t2_2;
    s32 var_a1_2;
    s32 var_a3;
    s32 var_a2;

    switch (gModeSelection) {
        case BATTLE:
        default:
            return; // HEY! returns, not breaks
        case GRAND_PRIX:
        case TIME_TRIALS:
            var_a2 = 8;
            break;
        case VERSUS:
            var_a2 = gPlayerCount;
            break;
    }

    for (var_a3 = 0; var_a3 < var_a2; var_a3++) {
        temp_a0 = gGPCurrentRacePlayerIdByRank[var_a3];
        sp50[var_a3] = temp_a0;
        gCourseCompletionPercentByRank[var_a3] = -gTimePlayerLastTouchedFinishLine[temp_a0];
    }

    for (var_a3 = 0; var_a3 < (var_a2 - 1); var_a3++) {
        for (var_a1_2 = var_a3 + 1; var_a1_2 < var_a2; var_a1_2++) {
            if (gCourseCompletionPercentByRank[var_a3] < gCourseCompletionPercentByRank[var_a1_2]) {
                temp_t2_2 = sp50[var_a3];
                sp50[var_a3] = sp50[var_a1_2];
                sp50[var_a1_2] = temp_t2_2;
                temp_f0 = gCourseCompletionPercentByRank[var_a3];
                gCourseCompletionPercentByRank[var_a3] = gCourseCompletionPercentByRank[var_a1_2];
                gCourseCompletionPercentByRank[var_a1_2] = temp_f0;
            }
        }
    }

    for (var_a3 = 0; var_a3 < NUM_PLAYERS; var_a3++) {
        gPreviousGPCurrentRaceRankByPlayerId[var_a3] = gGPCurrentRaceRankByPlayerId[var_a3];
    }

    for (var_a3 = 0; var_a3 < var_a2; var_a3++) {
        gGPCurrentRaceRankByPlayerId[sp50[var_a3]] = var_a3;
        gGPCurrentRacePlayerIdByRank[var_a3] = sp50[var_a3];
    }
}

void set_places_end_course_with_time(void) {
    f32 temp_a0;
    s32 temp;
    s32 sp68[8];
    UNUSED s32 pad;
    s32 temp_t1;
    s32 i;
    s32 j;
    s32 this_loops_upper_bound_is_brough_to_you_by_the_number = 8;

    for (i = 0; i < this_loops_upper_bound_is_brough_to_you_by_the_number;) {
        gCourseCompletionPercentByRank[i++] = 0.0f;
    }

    for (j = 0, i = 0; i < this_loops_upper_bound_is_brough_to_you_by_the_number; i++) {
        if (gPlayers[i].type & 0x800) {
            sp68[j] = i;
            gCourseCompletionPercentByRank[j] = -gTimePlayerLastTouchedFinishLine[i];
            j++;
        }
    }

    temp_t1 = j;
    for (i = 0; i < this_loops_upper_bound_is_brough_to_you_by_the_number; i++) {
        if (!(gPlayers[i].type & 0x800)) {
            sp68[j] = i;
            gCourseCompletionPercentByRank[j] = gCourseCompletionPercentByPlayerId[i];
            j++;
        }
    }

    for (i = 0; i < (temp_t1 - 1); i++) {
        for (j = i + 1; j < temp_t1; j++) {
            if (gCourseCompletionPercentByRank[i] < gCourseCompletionPercentByRank[j]) {
                temp = sp68[i];
                sp68[i] = sp68[j];
                sp68[j] = temp;
                temp_a0 = gCourseCompletionPercentByRank[i];
                gCourseCompletionPercentByRank[i] = gCourseCompletionPercentByRank[j];
                gCourseCompletionPercentByRank[j] = temp_a0;
            }
        }
    }

    for (i = temp_t1; i < (this_loops_upper_bound_is_brough_to_you_by_the_number - 1); i++) {
        for (j = i + 1; j < this_loops_upper_bound_is_brough_to_you_by_the_number; j++) {
            if (gCourseCompletionPercentByRank[i] < gCourseCompletionPercentByRank[j]) {
                temp = sp68[i];
                sp68[i] = sp68[j];
                sp68[j] = temp;
                temp_a0 = gCourseCompletionPercentByRank[i];
                gCourseCompletionPercentByRank[i] = gCourseCompletionPercentByRank[j];
                gCourseCompletionPercentByRank[j] = temp_a0;
            }
        }
    }

    for (i = 0; i < 8; i++) {
        gPreviousGPCurrentRaceRankByPlayerId[i] = gGPCurrentRaceRankByPlayerId[i];
    }

    for (i = 0; i < this_loops_upper_bound_is_brough_to_you_by_the_number; i++) {
        gGPCurrentRaceRankByPlayerId[sp68[i]] = i;
        gGPCurrentRacePlayerIdByRank[i] = sp68[i];
    }
}

/**
 * Checks if a path point is within a valid range of another path point, accounting for track wrapping
 *
 * @param pathPoint The path point to check
 * @param currentPathPoint The reference path point
 * @param backwardRange Number of path to look behind
 * @param forwardRange Number of path to look ahead
 * @param totalPathPoints Total number of path in the track
 * @return
 *   1: path point is within normal range
 *  -1: path point is within wrapped range
 *   2: path point is out of range
 *   0: invalid range parameters
 */
s32 is_path_point_in_range(u16 pathPoint, u16 currentPathPoint, u16 backwardRange, u16 forwardRange,
                           u16 totalPathPoints) {
    s32 var_v1;

    var_v1 = 0;
    if ((currentPathPoint >= backwardRange) && (currentPathPoint < (totalPathPoints - forwardRange))) {
        if ((pathPoint >= (currentPathPoint - backwardRange)) && ((currentPathPoint + forwardRange) >= pathPoint)) {
            var_v1 = 1;
        }
    } else if ((((currentPathPoint + forwardRange) % totalPathPoints) < pathPoint) &&
               ((((currentPathPoint + totalPathPoints) - backwardRange) % totalPathPoints) >= pathPoint)) {
        var_v1 = -1;
    } else {
        var_v1 = 2;
    }
    return var_v1;
}

void func_80007D04(s32 playerId, Player* player) {
    s16 temp_t1;
    s16 temp_t2;
    s32 var_v0;

    temp_t1 = gNumPathPointsTraversed[gBestRankedHumanPlayer];
    temp_t2 = gNumPathPointsTraversed[playerId];

    if (gGPCurrentRaceRankByPlayerId[playerId] < 2) {
        s16 val1 = gGPCurrentRaceRankByPlayerId[gBestRankedHumanPlayer];
        s16 val2 = temp_t2 - temp_t1;

        if (val2 > 400 && val1 >= 6) {
            player->effects &= ~0x200000;
            player_accelerate(player);
            D_801634C0[playerId] = 4;
            return;
        }
    } else {
        player->effects |= 0x200000;
        player_accelerate(player);
        D_801634C0[playerId] = 3;
        return;
    }

    switch (gCCSelection) { // WTF, FAKE ?
        case 3:
            break;
    }

    switch (gCCSelection) {
        case 0:
            var_v0 = 0;
            if (playerId == D_80163344[0]) {
                var_v0 = 0x14;
            }
            break;

        case 1:
            var_v0 = 8;
            if (playerId == D_80163344[0]) {
                var_v0 = 0x18;
            }
            break;

        case 2:
            var_v0 = 0x12;
            if (playerId == D_80163344[0]) {
                var_v0 = 0x24;
            }
            break;

        case 3:
            var_v0 = 8;
            if (playerId == D_80163344[0]) {
                var_v0 = 0x18;
            }
            break;

        default:
            var_v0 = 0;
            break;
    }

    if (temp_t2 < temp_t1) {
        player->effects |= 0x200000;
        player_accelerate(player);
        D_801634C0[playerId] = 1;
    } else if (temp_t2 < (temp_t1 + var_v0 + 0x32)) {
        player->effects &= ~0x200000;
        player_accelerate(player);
        D_801634C0[playerId] = 3;
    } else if (D_801631E0[playerId] == 0) {
        player->effects &= ~0x200000;
        player_accelerate(player);
        D_801634C0[playerId] = 2;
    } else {
        player->effects &= ~0x200000;
        player_decelerate(player, 1.0f);
        D_801634C0[playerId] = -1;
    }
}

void func_80007FA4(s32 arg0, Player* player, f32 arg2) {
    f32 temp_f0;
    f32 temp_f12;
    f32 temp_f2;
    s32 test;

    temp_f0 = D_80163418[arg0] - player->pos[0];
    temp_f2 = D_80163438[arg0] - player->pos[2];
    temp_f12 = (temp_f0 * temp_f0) + (temp_f2 * temp_f2);
    if (arg0 == 3) {
        if ((temp_f12 < 25.0f) && (D_80163410[arg0] < 5)) {
            D_80163410[arg0] = 4;
            (arg2 < ((2.0 * 18.0) / 216.0)) ? func_80038BE4(player, 1) : player_decelerate(player, 1.0f);
        } else if ((temp_f12 < 3600.0f) && (D_80163410[arg0] < 4)) {
            D_80163410[arg0] = 3;
            (arg2 < ((5.0 * 18.0) / 216.0)) ? func_80038BE4(player, 1) : player_decelerate(player, 5.0f);
        } else {
            (arg2 < ((20.0 * 18.0) / 216.0)) ? func_80038BE4(player, 10) : player_decelerate(player, 1.0f);
        }
    } else {
        if ((temp_f12 < 25.0f) && (D_80163410[arg0] < 5)) {
            D_80163410[arg0] = 4;
            test = 2;
            (arg2 < ((test * 18.0) / 216.0)) ? func_80038BE4(player, 1) : player_decelerate(player, 1.0f);
        } else if ((temp_f12 < 4900.0f) && (D_80163410[arg0] < 4)) {
            D_80163410[arg0] = 3;
            test = 5;
            (arg2 < ((test * 18.0) / 216.0)) ? func_80038BE4(player, 1) : player_decelerate(player, 15.0f);
        } else if ((temp_f12 < 22500.0f) && (D_80163410[arg0] < 3)) {
            D_80163410[arg0] = 2;
            test = 20;
            (arg2 < ((test * 18.0) / 216.0)) ? func_80038BE4(player, 5) : player_decelerate(player, 1.0f);
        } else if ((temp_f12 < 90000.0f) && (D_80163410[arg0] < 2)) {
            D_80163410[arg0] = 1;
            test = 30;
            (arg2 < ((test * 18.0) / 216.0)) ? func_80038BE4(player, 6) : player_decelerate(player, 1.0f);
        } else if (D_80163410[arg0] == 0) {
            test = 35;
            (arg2 < (((test ^ 0) * 18.0) / 216.0)) ? func_80038BE4(player, 2) : player_decelerate(player, 1.0f);
        } else {
            player_decelerate(player, 1.0f);
        }
    }
}

void regulate_cpu_speed(s32 playerId, f32 targetSpeed, Player* player) {
    f32 var_f2;
    f32 var_f0;
    UNUSED s32 thing;
    s32 var_a1;

    var_f2 = player->speed;
    if (!(player->effects & 0x80) && !(player->effects & 0x40) && !(player->effects & 0x20000) &&
        !(player->soundEffects & 0x400000) && !(player->soundEffects & 0x01000000) && !(player->soundEffects & 2) &&
        !(player->soundEffects & 4)) {
        if (IsPodiumCeremony()) {
            func_80007FA4(playerId, player, var_f2);
        } else if ((bStopAICrossing[playerId] == 1) && !(player->effects & (STAR_EFFECT | BOO_EFFECT))) {
            player_decelerate(player, 10.0f);
            if (player->currentSpeed == 0.0) {
                player->velocity[0] = 0.0f;
                player->velocity[2] = 0.0f;
            }
        } else {
            var_f0 = 3.3333333f;
            switch (gCCSelection) { /* irregular */
                case 1:
                case 3:
                    break;
                case 0:
                    var_f0 = 2.5f;
                    break;
                case 2:
                    var_f0 = 3.75f;
                    break;
            }
            if (var_f2 < var_f0) {
                player->effects &= ~0x00200000;
                player_accelerate(player);
            } else if (player->type & 0x800) {
                if (var_f2 < targetSpeed) {
                    player->effects &= ~0x00200000;
                    player_accelerate(player);
                } else {
                    player->effects &= ~0x00200000;
                    player_decelerate(player, 1.0f);
                }
            } else if ((D_801631E0[playerId] == 1) && (D_80163330[playerId] != 1)) {
                if (func_800088D8(playerId, gLapCountByPlayerId[playerId], gGPCurrentRaceRankByPlayerIdDup[playerId]) ==
                    1) {
                    player->effects |= 0x200000;
                    player_accelerate(player);
                } else {
                    player->effects &= ~0x00200000;
                    player_decelerate(player, 1.0f);
                }
            } else {
                var_a1 = 1;
                switch (gSpeedCPUBehaviour[playerId]) { /* switch 1; irregular */
                    case SPEED_CPU_BEHAVIOUR_FAST:      /* switch 1 */
                        player->effects &= ~0x00200000;
                        player_accelerate(player);
                        break;
                    case SPEED_CPU_BEHAVIOUR_MAX: /* switch 1 */
                        player->effects |= 0x200000;
                        player_accelerate(player);
                        break;
                    case SPEED_CPU_BEHAVIOUR_SLOW: /* switch 1 */
                        if (((var_f2 / 18.0f) * 216.0f) > 20.0f) {
                            targetSpeed = 1.6666666f;
                        }
                        var_a1 = 0;
                        break;
                    case SPEED_CPU_BEHAVIOUR_NORMAL: /* switch 1 */
                    default:                         /* switch 1 */
                        var_a1 = 0;
                        break;
                }
                if (var_a1 != 1) {
                    if (var_f2 < targetSpeed) {
                        if ((gDemoMode == 1) && (!IsPodiumCeremony())) {
                            player_accelerate(player);
                        } else if (D_80163330[playerId] == 1) {
                            func_80007D04(playerId, player);
                        } else if (func_800088D8(playerId, gLapCountByPlayerId[playerId],
                                                 gGPCurrentRaceRankByPlayerIdDup[playerId]) == 1) {
                            player->effects |= 0x200000;
                            player_accelerate(player);
                        } else {
                            player->effects &= ~0x00200000;
                            player_decelerate(player, 1.0f);
                        }
                    } else {
                        player->effects &= ~0x00200000;
                        if (targetSpeed > 1.0f) {
                            player_decelerate(player, 2.0f);
                        } else {
                            player_decelerate(player, 5.0f);
                        }
                    }
                }
            }
        }
    }
}

#ifdef NON_MATCHING
// generated by m2c commit 05102b12b75400ad2be8bd0872404237b36e1702 on Feb-20-2024
//  static ? gAIDistances;                                /* unable to generate initializer: unknown type */
//  static ? gMarioRacewayAIDistances;                                /* unable to generate initializer: unknown type */
//  static ? *D_800DCBB4[0x15] = {
//      &gMarioRacewayAIDistances,
//      &gAIDistances,
//      &gAIDistances,
//      &gAIDistances,
//      &gAIDistances,
//      &gAIDistances,
//      &gAIDistances,
//      &gAIDistances,
//      &gAIDistances,
//      &gAIDistances,
//      &gAIDistances,
//      &gAIDistances,
//      &gAIDistances,
//      &gAIDistances,
//      &gAIDistances,
//      &gAIDistances,
//      &gAIDistances,
//      &gAIDistances,
//      &gAIDistances,
//      &gAIDistances,
//      &gAIDistances,
//  };

bool func_800088D8(s32 playerId, s16 lapNum, s16 currRank) {
    Player* player;
    f32 interp;
    s16 rank;
    s16 var_a0_4;
    s16 var_t1;
    s16 var_v0;
    s16* temp_a3;
    s32 temp_a2;
    s32 arg1_times_8;
    s32 var_a0_2;
    s32 progress;
    s32 var_a1_4;
    s32 i;

    D_80163128[playerId] = -1;
    D_80163150[playerId] = -1;
    if (gModeSelection == 1) {
        return true;
    }
    if (lapNum < MIN_LAPS) {
        return true;
    } else if (lapNum > MAX_LAPS) {
        lapNum = MAX_LAPS;
    }
    if (D_80163330[playerId] == 1) {
        return true;
    }
    player = &gPlayers[playerId];
    if (player->type & 0x4000) {
        return true;
    }
    arg1_times_8 = lapNum * 8;
    temp_a3 = &CM_GetProps()->AIDistance[arg1_times_8];
    if (currRank == 0) {
        if (gDemoMode == 1) {
            temp_a2 = gNumPathPointsTraversed[playerId] - gNumPathPointsTraversed[D_80164378[7]];
            if (temp_a2 < 0) {
                temp_a2 = -temp_a2;
            }
            if (arg1_times_8 < 24) {
                interp = gLapCompletionPercentByPlayerId[playerId];
                D_80163150[playerId] = temp_a3[8] * interp + temp_a3[0] * (1.0f - interp);
            } else {
                D_80163150[playerId] = temp_a3[0];
            }
            if (D_80163150[playerId] < temp_a2 && player->speed / 18.0f * 216.0f >= 20.0f) {
                return false;
            }
            return true;
        }
        progress = gNumPathPointsTraversed[playerId] - gNumPathPointsTraversed[gBestRankedHumanPlayer];
        rank = gGPCurrentRaceRankByPlayerId[2 + (gBestRankedHumanPlayer * 4)];
        if (gPathCountByPathIndex[0] * 2 / 3 < progress && rank >= 6) {
            progress = gNumPathPointsTraversed[playerId] - gNumPathPointsTraversed[gLapCountByPlayerId[-26 + rank * 2]];
        }
        if (progress < 0) {
            progress = -progress;
        }
        if (arg1_times_8 < 24) {
            interp = gLapCompletionPercentByPlayerId[playerId];
            var_a0_2 = temp_a3[8] * interp + temp_a3[0] * (1.0f - interp);
        } else {
            var_a0_2 = temp_a3[0];
        }
        D_80163150[playerId] = (gCCSelection + 1) * var_a0_2;
        if (D_80163150[playerId] < progress && player->speed / 18.0f * 216.0f >= 20.0f) {
            return false;
        }
        return true;
    } else {
        var_a1_4 = gNumPathPointsTraversed[(s16) *D_80163344] - gNumPathPointsTraversed[playerId];
        if (var_a1_4 < 0) {
            var_a1_4 = -var_a1_4;
        }
        var_v0 = 0;
        for (i = 0; i < 2; i++) {
            if (gGPCurrentRaceRankByPlayerId[D_80163344[i]] < currRank) {
                var_v0++;
            }
        }
        var_a0_4 = 0;
        for (i = 0; i < gPlayerCount; i++) {
            if (gGPCurrentRaceRankByPlayerId[i] < currRank) {
                var_a0_4++;
            }
        }
        var_t1 = (currRank - var_v0) - var_a0_4;
        if (var_v0 > 0 || var_a0_4 > 0) {
            var_t1++;
        }
        D_80164538[playerId] = var_t1;
        if (var_t1 < 0 || var_t1 >= 8) {
            return false;
        }
        if (arg1_times_8 < 24) {
            temp_a3 = &temp_a3[var_t1];
            interp = gLapCompletionPercentByPlayerId[playerId];
            D_80163150[playerId] = temp_a3[8] * interp + temp_a3[0] * (1.0f - interp);
        } else {
            D_80163150[playerId] = temp_a3[var_t1];
        }
        D_80163128[playerId] = var_a1_4;
        if (D_80163150[playerId] < var_a1_4) {
            return true;
        }
        return false;
    }
}
#else
GLOBAL_ASM("asm/non_matchings/code_80005FD0/func_800088D8.s")
#endif

void set_current_path(s32 pathIndex) {
    gCurrentTrackPath = gTrackPaths[pathIndex];
    gCurrentTrackLeftPath = gTrackLeftPaths[pathIndex];
    gCurrentTrackRightPath = gTrackRightPaths[pathIndex];
    gCurrentTrackSectionTypesPath = gTrackSectionTypes[pathIndex];
    gCurrentPathPointExpectedRotationPath = gPathExpectedRotation[pathIndex];
    gCurrentTrackConsecutiveCurveCountsPath = gTrackConsecutiveCurveCounts[pathIndex];
    gSelectedPathCount = gPathCountByPathIndex[pathIndex];
}

s32 update_player_path_selection(s32 payerId, s32 pathIndex) {
    f32 posX;
    f32 posY;
    f32 posZ;
    Player* player;
    s32 trackSectionId;
    UNUSED s32 stackPadding;

    player = &gPlayers[payerId];
    posX = player->pos[0];
    posY = player->pos[1];
    posZ = player->pos[2];
    stackPadding = pathIndex;
    trackSectionId = get_track_section_id(player->collision.meshIndexZX);
    gPlayersTrackSectionId[payerId] = trackSectionId;
    sSomeNearestPathPoint = find_closest_path_point_track_section(posX, posY, posZ, trackSectionId, &pathIndex);
    gNearestPathPointByPlayerId[payerId] = sSomeNearestPathPoint;
    if (pathIndex) {};
    gPathIndexByPlayerId[payerId] = pathIndex;
    set_current_path(pathIndex);
    if (stackPadding) {};
    return pathIndex;
}

void update_player_completion(s32 playerId) {
    f32 temp_f0;

    gNumPathPointsTraversed[playerId] =
        (gLapCountByPlayerId[playerId] * gPathCountByPathIndex[0]) + sSomeNearestPathPoint;
    temp_f0 = (f32) gNearestPathPointByPlayerId[playerId] / (f32) gPathCountByPathIndex[gPathIndexByPlayerId[playerId]];
    gLapCompletionPercentByPlayerId[playerId] = temp_f0;
    gCourseCompletionPercentByPlayerId[playerId] = temp_f0;
    gCourseCompletionPercentByPlayerId[playerId] += gLapCountByPlayerId[playerId];
}

void yoshi_valley_cpu_path(s32 playerId) {
    s16 temp_v0;

    temp_v0 = bInMultiPathSection[playerId];
    if (sSomeNearestPathPoint >= 0x6D) {
        bInMultiPathSection[playerId] = 1;
        switch (gPlayerPathIndex) {
            case 0:
                if (sSomeNearestPathPoint >= 0x20F) {
                    bInMultiPathSection[playerId] = 0;
                }
                break;
            case 1:
                if (sSomeNearestPathPoint >= 0x206) {
                    bInMultiPathSection[playerId] = 0;
                }
                break;
            case 2:
                if (sSomeNearestPathPoint >= 0x211) {
                    bInMultiPathSection[playerId] = 0;
                }
                break;
            case 3:
                if (sSomeNearestPathPoint >= 0x283) {
                    bInMultiPathSection[playerId] = 0;
                }
                break;
        }
    }
    if ((temp_v0 == 0) && (bInMultiPathSection[playerId] == 1)) {
        cpu_enteringPathIntersection[playerId] = 1;
    }
    if ((temp_v0 == 1) && (bInMultiPathSection[playerId] == 0)) {
        cpu_exitingPathIntersection[playerId] = 1;
    }
}

void update_cpu_path_completion(s32 playerId, Player* player) {
    f32 posX;
    f32 posY;
    f32 posZ;
    UNUSED f32 pad[2];

    posX = player->pos[0];
    posY = player->pos[1];
    posZ = player->pos[2];
    if (cpu_enteringPathIntersection[playerId] == 1) {
        gPlayerPathIndex = update_player_path_selection(playerId, random_int(4U));
        sSomeNearestPathPoint = update_player_path(posX, posY, posZ, 0, player, playerId, gPlayerPathIndex);
        gNearestPathPointByPlayerId[playerId] = sSomeNearestPathPoint;
        update_player_completion(playerId);
        cpu_enteringPathIntersection[playerId] = 0;
    }
    if (cpu_exitingPathIntersection[playerId] == 1) {
        gPlayerPathIndex = update_player_path_selection(playerId, 0);
        sSomeNearestPathPoint = update_player_path(posX, posY, posZ, 0, player, playerId, gPlayerPathIndex);
        gNearestPathPointByPlayerId[playerId] = sSomeNearestPathPoint;
        update_player_completion(playerId);
        cpu_exitingPathIntersection[playerId] = 0;
    }
}

/**
 * Helps calculate time since player last touched finishline.
 **/
f32 func_80009258(UNUSED s32 playerId, f32 arg1, f32 arg2) {
    f32 temp_f2 = gPathStartZ - arg2;
    f32 temp_f12 = arg1 - gPathStartZ;
    return gCourseTimer - ((COURSE_TIMER_ITER_f * temp_f2) / (temp_f2 + temp_f12));
}

#ifdef NON_MATCHING
// Weird stack issue, can't figure out how to get rid of it
void update_player_path_completion(s32 playerId, Player* player) {
    f32 tempPos0;
    f32 tempPos1;
    f32 tempPos2;
    s32 var_v0;
    s32 var_v1;
    f32 someTemp0;
    UNUSED s16 stackPadding0;
    s16 var_t0;
    UNUSED s32 stackPadding1;

    tempPos0 = player->pos[0];
    tempPos1 = player->pos[1];
    tempPos2 = player->pos[2];
    gIsPlayerNewPathPoint = 0;
    D_80163240[playerId] = 0;
    sSomeNearestPathPoint = update_player_path(tempPos0, tempPos1, tempPos2, gNearestPathPointByPlayerId[playerId],
                                               player, playerId, gPlayerPathIndex);
    gCurrentNearestPathPoint = sSomeNearestPathPoint;
    if (gNearestPathPointByPlayerId[playerId] != sSomeNearestPathPoint) {
        gNearestPathPointByPlayerId[playerId] = sSomeNearestPathPoint;
        gIsPlayerNewPathPoint = 1;
        update_player_completion(playerId);
    }
    if (IsPodiumCeremony()) {
        update_player_position_factor(playerId, sSomeNearestPathPoint, gPlayerPathIndex);
        return;
    }
    if ((sSomeNearestPathPoint < 0x14) || ((gPathCountByPathIndex[gPlayerPathIndex] - 0x14) < sSomeNearestPathPoint) ||
        (IsKalimariDesert())) {
        var_v1 = 0;
        var_t0 = 0;
        if (IsKalimariDesert()) {
            D_801634EC = 0;
            if (player->effects & 0x200) {
                D_801634EC = 1;
            }
            if (gIsMirrorMode != 0) {
                if (tempPos0 < 300.0f) {
                    var_v1 = 1;
                    var_t0 = 1;
                } else if ((tempPos0 < 1300.0f) && (gLapCountByPlayerId[playerId] < 2) && ((D_801634EC == 1))) {
                    var_v1 = 1;
                    var_t0 = 1;
                }
            } else {
                if (tempPos0 > -300.0f) {
                    var_v1 = 1;
                    var_t0 = 1;
                } else {
                    if ((tempPos0 > -1300.0f) && (gLapCountByPlayerId[playerId] < 2) && (D_801634EC == 1)) {
                        var_v1 = 1;
                        var_t0 = 1;
                    }
                }
            }
        } else {
            var_v1 = 1;
            var_t0 = 1;
        }
        someTemp0 = gPreviousPlayerZ[playerId];
        if ((var_v1 != 0) && (tempPos2 <= gPathStartZ)) {
            if (gPathStartZ < someTemp0) {
                gLapCountByPlayerId[playerId]++;
                if ((gModeSelection == 0) && (gLapCountByPlayerId[playerId] == 5)) {
                    if (gGPCurrentRaceRankByPlayerIdDup[playerId] == 7) {
                        for (var_v0 = 0; var_v0 < 8; var_v0++) {
                            gLapCountByPlayerId[var_v0]--;
                        }
                    }
                }
                D_80163240[playerId] = 1;
                update_player_completion(playerId);
                reset_cpu_behaviour(playerId);
                cpu_ItemStrategy[playerId].numItemUse = 0;
                if ((D_8016348C == 0) && !(player->type & 0x800)) {
                    gTimePlayerLastTouchedFinishLine[playerId] = func_80009258(playerId, someTemp0, tempPos2);
                }
            }
        }
        if ((var_t0 != 0) && (someTemp0 <= gPathStartZ) && (gPathStartZ < tempPos2)) {
            gLapCountByPlayerId[playerId]--;
            update_player_completion(playerId);
        }
    }
    gPreviousPlayerZ[playerId] = tempPos2;
    if ((IsYoshiValley()) && (gIsPlayerNewPathPoint == 1)) {
        yoshi_valley_cpu_path(playerId);
        if (((player->type & 0x4000) == 0) || (player->type & 0x1000)) {
            update_cpu_path_completion(playerId, player);
        }
    }
    if ((player->type & 0x4000) && !(player->type & 0x1000)) {
        detect_wrong_player_direction(playerId, player);
        if ((gModeSelection == 0) && (gPlayerCount == 2) && (playerId == 0)) {
            if (gGPCurrentRaceRankByPlayerIdDup[0] < gGPCurrentRaceRankByPlayerIdDup[1]) {
                gBestRankedHumanPlayer = 0;
            } else {
                gBestRankedHumanPlayer = 1;
            }
        }
    } else {
        //????
    }
    update_player_position_factor(playerId, sSomeNearestPathPoint, gPlayerPathIndex);
}
#else
GLOBAL_ASM("asm/non_matchings/code_80005FD0/update_player_path_completion.s")
#endif

void update_vehicles(void) {
    s32 i;
    generate_player_smoke();
    D_8016337C++;

    // CM_TickBombKarts();
    // CM_VehiclesTick();
}

void play_cpu_sound_effect(s32 arg0, Player* player) {
    if (D_80163398[arg0] >= 0xB) {
        if ((player->effects & 0x80) || (player->effects & 0x40) || (player->effects & 0x20000)) {
            func_800C92CC(arg0, SOUND_ARG_LOAD(0x29, 0x00, 0x80, 0x0A));
            D_80163398[arg0] = 0;
        }
    }
    if (D_801633B0[arg0] >= 0xB) {
        if ((player->soundEffects & REVERSE_EFFECT) || (player->soundEffects & 0x01000000) ||
            (player->soundEffects & 2) || (player->soundEffects & 4) || (player->effects & HIT_EFFECT)) {
            func_800C92CC(arg0, SOUND_ARG_LOAD(0x29, 0x00, 0x80, 0x0B));
            D_801633B0[arg0] = 0;
        }
    }
}

void update_player_timer_sound(s32 playerId, UNUSED Player* unused) {
    s32 var_s0;

    if (D_801633C8[playerId] >= 0x65) {
        for (var_s0 = 0; var_s0 < gPlayerCount; var_s0++) {
            if ((gGPCurrentRaceRankByPlayerId[playerId] < gGPCurrentRaceRankByPlayerId[var_s0]) &&
                (gGPCurrentRaceRankByPlayerId[playerId] == gPreviousGPCurrentRaceRankByPlayerId[var_s0]) &&
                (gGPCurrentRaceRankByPlayerId[var_s0] == gPreviousGPCurrentRaceRankByPlayerId[playerId])) {
                func_800C92CC(playerId, 0x2900800DU);
                D_801633C8[playerId] = 0;
            }
        }
    }
    if (D_801633C8[playerId] < 0x3E8) {
        D_801633C8[playerId]++;
    }
    if (D_80163398[playerId] < 0xC8) {
        D_80163398[playerId]++;
    }
    if (D_801633B0[playerId] < 0xC8) {
        D_801633B0[playerId]++;
    }
}

#ifdef NON_MATCHING
#define FAKEMATCH1 0
#define FAKEMATCH2 0
// https://decomp.me/scratch/uJCh3
// Lots of register allocation differences, but messing around seems to suggest they stem from 2 specific areas
// MISMATCH1: something about the loading of `playerId` is off
// MISMATCH2: something about the handling of the math is off. Not sure exactly what though
// MISMATCH3: there's a small instruction ordering issue concerning `gCourseMaxX`. No idea what to do about it
// FAKEMATCH1 is the best improvement I've seen yet, MISMATCH2/3 become the only issues.

void update_player(s32 playerId) {
    UNUSED s32 stackPadding00;
    UNUSED s32 stackPadding01;
    UNUSED s32 stackPadding02;
    UNUSED s32 stackPadding03;
    UNUSED s32 stackPadding04;
    UNUSED s32 stackPadding05;
    UNUSED s32 stackPadding06;
    UNUSED s32 stackPadding07;
    UNUSED s32 stackPadding08;
    UNUSED s32 stackPadding09;
    UNUSED s32 stackPadding0A;
    UNUSED s32 stackPadding0B;
    UNUSED s32 stackPadding0C;
    UNUSED s32 stackPadding0D;
    s16 var_a0_2;
    s16 var_a1;
    UNUSED s32 stackPadding0E;
    UNUSED s32 stackPadding0F;
    UNUSED s32 stackPadding10;
    UNUSED s32 stackPadding11;
    UNUSED s32 stackPadding12;
    UNUSED s32 stackPadding13;
    UNUSED s32 stackPadding14;
    UNUSED s32 stackPadding15;
    UNUSED s32 stackPadding16;
    UNUSED s32 stackPadding17;
    UNUSED s32 stackPadding18;
    u16 stackPadding19;
    u16 stackPadding1A;
    f32 temp_f0;
    f32 temp_f2;
    s16 var_a2;
    s16 var_v1;
    s32 temp_f6;
    Player* player;
    TrackPathPoint* waypoint;
    f32 athing = 1.5f;

    player = &gPlayers[playerId];
    if (CM_GetProps()->AIMaximumSeparation >= 0.0f) {
        D_80163100[playerId] += 1;
        if (playerId == 0) {
            gIncrementUpdatePlayer++;
            if (gIncrementUpdatePlayer & 1) {
                D_80163488 += 1;
            }
        }
        if (!(player->type & 0x8000)) {
            gNumPathPointsTraversed[playerId] = -0x00000014;
            gCourseCompletionPercentByPlayerId[playerId] = -1000.0f;
            gLapCompletionPercentByPlayerId[playerId] = -1000.0f;
            return;
        }
        D_801633E0[playerId] = 0;
        if (player->pos[0] < gCourseMinX) {
            D_801633E0[playerId] = 1;
        }
        if (gCourseMaxX < player->pos[0]) {
            D_801633E0[playerId] = 2;
        }
        if (player->pos[2] < gCourseMinZ) {
            D_801633E0[playerId] = 3;
        }
        // MISMATCH3
        if (gCourseMaxZ < player->pos[2]) {
            D_801633E0[playerId] = 4;
        }
        if (!(player->unk_0CA & 2) && !(player->unk_0CA & 8)) {
            gPlayerPathIndex = gPathIndexByPlayerId[playerId];
            set_current_path(gPlayerPathIndex);
            // if (GetCourse() == GetKalimariDesert()) {
            CM_VehicleCollision(playerId, player);
            // handle_trains_interactions(playerId, player);
            if (playerId == 0) {
                CM_CrossingTrigger();
                // func_80013054();
            }
            //}
            if (IsDkJungle()) {
                // handle_paddle_boats_interactions(player);
            } else if (IsToadsTurnpike()) {
                handle_box_trucks_interactions(playerId, player);
                handle_school_buses_interactions(playerId, player);
                handle_tanker_trucks_interactions(playerId, player);
                handle_cars_interactions(playerId, player);
            }
            if (player->type & 0x800) {
                player->effects &= ~0x00400000;
                player->unk_044 &= ~0x0001;
            }
            update_player_path_completion(playerId, player);
            if ((!IsPodiumCeremony()) && ((D_80163240[playerId] == 1) || (playerId == 0))) {
                set_places();
            }
            if (player->type & 0x1000) {
                if ((gIsPlayerNewPathPoint == 1) && (!IsPodiumCeremony())) {
                    cpu_behaviour(playerId);
                }
                if ((playerId & 1) != (gIncrementUpdatePlayer & 1)) {
                    cpu_use_item_strategy(playerId);
                }
                update_player_timer_sound(playerId, player);
                D_80162FD0 = 0;
                switch (gModeSelection) { /* switch 1; irregular */
                    case 1:               /* switch 1 */
                    case 2:               /* switch 1 */
                    case 3:               /* switch 1 */
                        break;
                    case 0: /* switch 1 */
                        break;
                }
                D_801631E0[playerId] = 0;
                if ((player->effects & 0x1000) && (!IsPodiumCeremony())) {
                    D_801631E0[playerId] = 1;
                }
                if ((D_801646CC == 1) || (player->type & 0x800) || (IsPodiumCeremony())) {
                    if (!IsToadsTurnpike()) {
                        gPlayerTrackPositionFactorInstruction[playerId].target = 0.0f;
                    }
                    gPlayerTrackPositionFactorInstruction[playerId].unkC = 0.0f;
                }
                if (gPlayerPathIndex > 0) {
                    gPlayerTrackPositionFactorInstruction[playerId].target = 0.0f;
                    gPlayerTrackPositionFactorInstruction[playerId].unkC = 0.0f;
                }
                // gNearestPathPointByPlayerId[playerId] might need to be saved to a temp
                gPlayerPathY[playerId] =
                    gTrackPaths[gPlayerPathIndex][gNearestPathPointByPlayerId[playerId]].posY + 4.3f;
                if ((D_801631F8[playerId] == 1) && (D_801631E0[playerId] == 0)) {
                    set_player_height(player);
                }
                if (D_801631E0[playerId] == 1) {
                    player->pos[1] = gPlayerPathY[playerId];
                }
                D_801631F8[playerId] = D_801631E0[playerId];

                // Old vehicle draw method was here

                if ((IsYoshiValley()) || (IsPodiumCeremony())) {
                    gPlayerTrackPositionFactorInstruction[playerId].target = 0.0f;
                } else if (IsToadsTurnpike()) {
                    // update_player_track_position_factor_from_box_trucks(playerId);
                    // update_player_track_position_factor_from_buses(playerId);
                    // update_player_track_position_factor_from_tanker_truck(playerId);
                    // update_player_track_position_factor_from_cars(playerId);
                }

                if (D_801631E0[playerId] == 1) {
                    D_801630E8[playerId] = 0;
                    player->effects &= ~0x10;
                    if ((playerId & 1) != (gIncrementUpdatePlayer & 1)) {
                        apply_cpu_turn(player, 0);
                        regulate_cpu_speed(playerId, gPreviousCpuTargetSpeed[playerId], player);
                        return;
                    }
                    if ((gPlayerCount > 0) && (gPlayerCount < 3) && (D_80163330[playerId] == 1) &&
                        (D_8016334C[playerId] < gGPCurrentRaceRankByPlayerId[playerId])) {
                        gPreviousCpuTargetSpeed[playerId] = 8.333333f;
                    } else if (D_80162FD0 == (s16) 1U) {
                        gPreviousCpuTargetSpeed[playerId] = CM_GetProps()->D_0D0096B8[gCCSelection];
                        gPlayerTrackPositionFactorInstruction[playerId].target = -0.5f;
                    } else if (gCurrentTrackConsecutiveCurveCountsPath[sSomeNearestPathPoint] > 0) {
                        gPreviousCpuTargetSpeed[playerId] = CM_GetProps()->CurveTargetSpeed[gCCSelection];
                    } else {
                        gPreviousCpuTargetSpeed[playerId] = CM_GetProps()->NormalTargetSpeed[gCCSelection];
                    }
                    CM_AICrossingBehaviour(playerId);
                    // check_ai_crossing_distance(playerId);
                    cpu_track_position_factor(playerId);
                    determine_ideal_cpu_position_offset(playerId, gCurrentNearestPathPoint);
                    temp_f0 = gOffsetPosition[0] - player->pos[0];
                    temp_f2 = gOffsetPosition[2] - player->pos[2];
                    if (!(player->effects & 0x80) && !(player->effects & 0x40) && !(player->effects & 0x800)) {
                        if (((temp_f0 * temp_f0) + (temp_f2 * temp_f2)) > 6400.0f) {
                            if (gPlayerPathIndex == 0) {
                                func_8000B140(playerId);
                                if (D_80162FF8[playerId] > 0) {
                                    stackPadding1A = gCurrentNearestPathPoint + 5;
                                    stackPadding1A %= gSelectedPathCount;
                                    set_track_offset_position(stackPadding1A, D_80163090[playerId], gPlayerPathIndex);
                                }
                            }
                            player->rotation[1] = -get_angle_between_two_vectors(player->pos, gOffsetPosition);
                        } else {
                            player->rotation[1] =
                                gPathExpectedRotation[gPlayerPathIndex]
                                                     [(gCurrentNearestPathPoint + 4) % gSelectedPathCount];
                        }
                    }
                    apply_cpu_turn(player, 0);
                    regulate_cpu_speed(playerId, gPreviousCpuTargetSpeed[playerId], player);
                    return;
                }
                if ((D_801630E8[playerId] == 1) || (D_801630E8[playerId] == -1)) {
                    player->effects |= 0x10;
                }
                if (D_801630E8[playerId] != 0) {
                    sPlayerAngle[playerId] = -get_angle_between_two_vectors(&player->oldPos[0], player->pos);
                    var_a0_2 =
                        (gCurrentPathPointExpectedRotationPath[(sSomeNearestPathPoint + 2) % gSelectedPathCount] *
                         0x168) /
                        65535;
                    var_a1 = (sPlayerAngle[playerId] * 0x168) / 65535;
                    if (var_a0_2 < -0xB4) {
                        var_a0_2 += 0x168;
                    }
                    if (var_a0_2 > 0xB4) {
                        var_a0_2 -= 0x168;
                    }
                    if (var_a1 < -0xB4) {
                        var_a1 += 0x168;
                    }
                    if (var_a1 > 0xB4) {
                        var_a1 -= 0x168;
                    }
                    var_v1 = var_a0_2 - var_a1;
                    if (var_v1 < -0xB4) {
                        var_v1 += 0x168;
                    }
                    if (var_v1 > 0xB4) {
                        var_v1 -= 0x168;
                    }
                    switch (D_801630E8[playerId]) {
                        case -1:
                            if (var_v1 > 5) {
                                D_801630E8[playerId] = 0;
                                player->effects &= ~0x10;
                            }
                            break;
                        case 1:
                            if (var_v1 < -5) {
                                D_801630E8[playerId] = 0;
                                player->effects &= ~0x10;
                            }
                            break;
                        default:
                            break;
                    }
                }
                // MISMATCH1
#if FAKEMATCH2 == 1
                stackPadding00 = (playerId & 1) != (gIncrementUpdatePlayer & 1);
                if (stackPadding00) {
#else
                if ((playerId & 1) != (gIncrementUpdatePlayer & 1)) {
#endif
                    apply_cpu_turn(player, gPreviousAngleSteering[playerId]);
                    regulate_cpu_speed(playerId, gPreviousCpuTargetSpeed[playerId], player);
                    return;
                }
                gIsPlayerInCurve[playerId] = are_in_curve(playerId, sSomeNearestPathPoint);
                determine_ideal_cpu_position_offset(playerId, sSomeNearestPathPoint);
                if (!IsPodiumCeremony()) {
                    if (gNumPathPointsTraversed[playerId] < 0xB) {
                        stackPadding1A = gCurrentNearestPathPoint;
                        if ((gNumPathPointsTraversed[playerId] > 0) && (IsToadsTurnpike())) {
                            stackPadding1A += 0x14;
                            stackPadding1A %= gSelectedPathCount;
                            set_track_offset_position(stackPadding1A, 0.0f, 0);
                            gPlayerTrackPositionFactorInstruction[playerId].target = 0.0f;
                        } else {
                            stackPadding1A += 8;
                            stackPadding1A %= gSelectedPathCount;
                            set_track_offset_position(stackPadding1A, gTrackPositionFactor[playerId], gPlayerPathIndex);
                            gPlayerTrackPositionFactorInstruction[playerId].current = gTrackPositionFactor[playerId];
                        }
                    }
                    if ((D_80162FD0 == 1) && (D_80162FF8[playerId] == 0)) {
                        stackPadding1A = gCurrentNearestPathPoint + 7;
                        stackPadding1A %= gSelectedPathCount;
                        set_track_offset_position(stackPadding1A, -0.7f, gPlayerPathIndex);
                    }
                    if (gPlayerPathIndex == 0) {
                        func_8000B140(playerId);
                        if (D_80162FF8[playerId] > 0) {
                            stackPadding1A = gCurrentNearestPathPoint + 5;
#if FAKEMATCH1 == 1
                            if (1) {}
                            if (1) {}
                            if (1) {}
                            if (1) {}
                            if (1) {}
                            if (1) {}
#endif
                            stackPadding1A %= gSelectedPathCount;
                            set_track_offset_position(stackPadding1A, D_80163090[playerId], gPlayerPathIndex);
                        }
                    }
                }
                if (IsPodiumCeremony()) {
                    switch (D_80163410[playerId]) { /* switch 3; irregular */
                        case 3:                     /* switch 3 */
                            gOffsetPosition[0] = D_80163418[playerId];
                            gOffsetPosition[2] = D_80163438[playerId];
                            break;
                        case 4: /* switch 3 */
                            waypoint = &gTrackPaths[playerId][(gNearestPathPointByPlayerId[playerId] + 0xA) %
                                                              gPathCountByPathIndex[playerId]];
                            gOffsetPosition[0] = waypoint->posX;
                            gOffsetPosition[2] = waypoint->posZ;
                            break;
                    }
                }
                gOffsetPosition[0] = (gPreviousPlayerAiOffsetX[playerId] + gOffsetPosition[0]) * 0.5f;
                gOffsetPosition[2] = (gPreviousPlayerAiOffsetZ[playerId] + gOffsetPosition[2]) * 0.5f;
                gPreviousPlayerAiOffsetX[playerId] = gOffsetPosition[0];
                gPreviousPlayerAiOffsetZ[playerId] = gOffsetPosition[2];
                temp_f2 = athing * 182.0f;
                temp_f6 = -athing * 182.0f;
                // MISMATCH2
                // This fixes part of the register allocation problems, makes fixing others
                // harder though. Needs more investigation
                // var_a2 = (-get_angle_between_two_vectors(player->pos, gOffsetPosition)) - (var_a1 =
                // player->rotation[1]);
                stackPadding19 = -get_angle_between_two_vectors(player->pos, gOffsetPosition) - player->rotation[1];
                var_a1 = stackPadding19;
                var_a2 = var_a1;
                if ((s16) temp_f2 < var_a1) {
                    var_a2 = temp_f2;
                }
                if (var_a2 < (s16) temp_f6) {
                    var_a2 = temp_f6;
                }

                var_v1 = CM_GetProps()->AISteeringSensitivity;

                switch (gCurrentTrackSectionTypesPath[playerId]) { /* switch 4; irregular */
                    case 2:                                        /* switch 4 */
                        if (gTrackPositionFactor[playerId] > (0.5f * 1.0f)) {
                            var_v1 = 0x0014;
                        }
                        if (gTrackPositionFactor[playerId] < -0.5f) {
                            var_v1 = 0x0035;
                        }
                        break;
                    case 3: /* switch 4 */
                        if (gTrackPositionFactor[playerId] > 0.5f) {
                            var_v1 = 0x0035;
                        }
                        if (gTrackPositionFactor[playerId] < -0.5f) {
                            var_v1 = 0x0014;
                        }
                        break;
                }
                if ((cpu_BehaviourState[playerId] == CPU_BEHAVIOUR_STATE_RUNNING) &&
                    ((gTrackPositionFactor[playerId] > 0.9f) || (gTrackPositionFactor[playerId] < -0.9f))) {
                    D_801630E8[playerId] = 0;
                    player->effects &= ~0x10;
                }
                if (player->effects & 2) {
                    switch (D_801630E8[playerId]) {
                        case 1:
                            var_a1 = 0x0035;
                            break;
                        case -1:
                            var_a1 = -0x0035;
                            break;
                        default:
                            var_a1 = (gPreviousAngleSteering[playerId] + ((var_a2 * var_v1) / temp_f2)) / 2;
                            break;
                    }
                } else if (player->effects & 0x1000000C) {
                    var_a1 = 0;
                } else {
                    var_a1 = (gPreviousAngleSteering[playerId] + ((var_a2 * var_v1) / temp_f2)) / 2;
                }
                apply_cpu_turn(player, var_a1);
                gPreviousAngleSteering[playerId] = var_a1;
                if ((gIsPlayerInCurve[playerId] == true) || (D_801630E8[playerId] == 1) ||
                    (D_801630E8[playerId] == -1) || (player->effects & 0x1000000C)) {
                    cpu_TargetSpeed[playerId] = CM_GetProps()->CurveTargetSpeed[gCCSelection];
                } else {
                    cpu_TargetSpeed[playerId] = CM_GetProps()->NormalTargetSpeed[gCCSelection];
                }
                if ((gTrackPositionFactor[playerId] > 0.9f) || (gTrackPositionFactor[playerId] < -0.9f)) {
                    cpu_TargetSpeed[playerId] = CM_GetProps()->OffTrackTargetSpeed[gCCSelection];
                }
                if (D_80162FD0 == 1) {
                    cpu_TargetSpeed[playerId] = CM_GetProps()->D_0D0096B8[gCCSelection];
                }

                if (CVarGetInteger("gEnableCustomCC", 0) == 1) {
#define calc_a(x, y, x2, y2) (y2 - y) / (x2 - x)
#define calc_b(x, y, b) y - (b * x)
                    f32 a;
                    f32 b;

#define calc(table)                                   \
    a = calc_a(50, table[CC_50], 150, table[CC_150]); \
    b = calc_b(50, table[CC_50], a);                  \
    cpu_TargetSpeed[playerId] = a * CVarGetFloat("gCustomCC", 150.0f) + b;
                    if ((gIsPlayerInCurve[playerId] == true) || (D_801630E8[playerId] == 1) ||
                        (D_801630E8[playerId] == -1) || (player->effects & 0x1000000C)) {
                        calc(CM_GetProps()->CurveTargetSpeed);
                    } else {
                        calc(CM_GetProps()->NormalTargetSpeed);
                    }
                    if ((gTrackPositionFactor[playerId] > 0.9f) || (gTrackPositionFactor[playerId] < -0.9f)) {
                        calc(CM_GetProps()->OffTrackTargetSpeed);
                    }
                    if (D_80162FD0 == 1) {
                        calc(CM_GetProps()->D_0D0096B8);
                    }
#undef calc_a
#undef calc_b
#undef calc
                }

                if ((D_801630E8[playerId] == 2) || (D_801630E8[playerId] == -2) || (D_801630E8[playerId] == 3)) {
                    cpu_TargetSpeed[playerId] = 3.3333333f;
                }
                if (CVarGetInteger("gHarderCPU", 0) == 1) {
                    cpu_TargetSpeed[playerId] = player->topSpeed * 1.5f;
                }
                gCurrentCpuTargetSpeed = cpu_TargetSpeed[playerId];
                player->effects &= ~0x00200000;
                gPreviousCpuTargetSpeed[playerId] = gCurrentCpuTargetSpeed;
                CM_AICrossingBehaviour(playerId);
                // check_ai_crossing_distance(playerId);
                regulate_cpu_speed(playerId, gCurrentCpuTargetSpeed, player);
            }
        }
    }
}
#undef FAKEMATCH1
#undef FAKEMATCH2
#else
GLOBAL_ASM("asm/non_matchings/code_80005FD0/update_player.s")
#endif

#ifdef NON_MATCHING
// Lots of work left to do, might be funtionally accurate?
void func_8000B140(s32 playerId) {
    UNUSED f32 stackPadding0;
    UNUSED f32 stackPadding1;
    UNUSED f32 stackPadding2;
    UNUSED f32 stackPadding3;
    UNUSED f32 stackPadding4;
    UNUSED f32 stackPadding5;
    UNUSED f32 stackPadding6;
    UNUSED f32 stackPadding7;
    f32 temp_f0_2;
    f32 temp_f12;
    f32 temp_f14;
    f32 temp_f16;
    f32 temp_f22;
    f32 temp_f2;
    f32 var_f18;
    f32 var_f20;
    s16 spB0[8];
    UNUSED f32 stackPadding8;
    s16 sp9C[8];
    UNUSED f32 stackPadding9;
    f32 sp74[8];
    s32 temp_a1_2;
    s16 temp_v1_2;
    s16 var_t1;
    s32 var_t2;
    s32 someIndex;
    Player* player;

    player = &gPlayers[playerId];
    if (!(player->effects & 0x10) && (D_801630E8[playerId] != 1) && (D_801630E8[playerId] != -1) &&
        !(gTrackPositionFactor[playerId] < -1.0f) && !(gTrackPositionFactor[playerId] > 1.0f) &&
        (player->characterId != 5) && (player->characterId != 7) && (player->characterId != 4) &&
        !(player->effects & STAR_EFFECT)) {
        var_t1 = gNearestPathPointByPlayerId[playerId];
        temp_f22 = (player->speed / 18.0f) * 216.0f;
        for (someIndex = 0; someIndex < 8; someIndex++) {
            sp9C[someIndex] = -1;
            spB0[someIndex] = 0x03E8;
        }
        if (D_80163010[playerId] > 0) {
            D_80163010[playerId]--;
            if (D_80163010[playerId] <= 0) {
                D_80162FF8[playerId] = 0;
            }
        }
        var_t2 = 0;
        for (someIndex = 0; (var_t2 < 2) && (someIndex < 8); someIndex++) {
            if (someIndex == playerId)
                continue;
            player = &gPlayers[someIndex];
            if (!(player->type & 0x8000))
                continue;
            temp_f2 = (temp_f22) -5.0f;
            temp_v1_2 = gNearestPathPointByPlayerId[someIndex];
            temp_f0_2 = (player->speed / 18.0f) * 216.0f;
            if (temp_f0_2 < temp_f2) {
                if (is_path_point_in_range(temp_v1_2, var_t1, 0U, 0x0014U, gSelectedPathCount) > 0) {
                    temp_a1_2 = temp_v1_2 - var_t1;
                    sp9C[var_t2] = someIndex;
                    if (temp_a1_2 > 0) {
                        spB0[var_t2] = temp_a1_2;
                    } else {
                        spB0[var_t2] = (temp_v1_2 + gSelectedPathCount) - var_t1;
                    }
                    sp74[var_t2] = temp_f2 - temp_f0_2;
                    var_t2 += 1;
                }
            }
        }
        if (var_t2 != 0) {
            var_f18 = 1.0f;
            var_f20 = -1.0f;
            for (someIndex = 0; someIndex < var_t2; someIndex++) {
                temp_f2 = gTrackPositionFactor[sp9C[someIndex]];
                if ((temp_f2 > -1.0f) && (temp_f2 < 1.0f)) {
                    temp_f12 = sp74[someIndex] + 10.0f;
                    temp_f12 *= 0.2f * (20.0f / (spB0[someIndex] + 20.0f));
                    temp_f12 /= 20.0f;
                    if ((var_f18 == 1.0f) && (var_f20 == -1.0f)) {
                        var_f18 = temp_f2 - temp_f12;
                        var_f20 = temp_f2 + temp_f12;
                    } else {
                        temp_f14 = temp_f2 - temp_f12;
                        temp_f16 = temp_f2 + temp_f12;
                        if ((temp_f14 < 1.0f) && (temp_f16 > 1.0f)) {
                            var_f18 = temp_f14;
                        }
                        if ((temp_f16 > -1.0f) && (temp_f14 < -1.0f)) {
                            var_f20 = temp_f16;
                        }
                    }
                }
            }
            if (!(var_f20 < var_f18) && !(gTrackPositionFactor[playerId] < var_f18) &&
                !(var_f20 < gTrackPositionFactor[playerId])) {
                if (var_f20 > 1.0f) {
                    var_f20 = 1.0f;
                }
                if (var_f18 < -1.0f) {
                    var_f18 = -1.0f;
                }
                if ((var_f18 + 1.0f) < (1.0f - var_f20)) {
                    D_80163010[playerId] = 0x003C;
                    D_80162FF8[playerId] = 1;
                    D_80163090[playerId] = var_f20;
                } else {
                    D_80163010[playerId] = 0x003C;
                    D_80162FF8[playerId] = 2;
                    D_80163090[playerId] = var_f18;
                }
            }
        }
    }
}
#else
GLOBAL_ASM("asm/non_matchings/code_80005FD0/func_8000B140.s")
#endif

s32 are_in_curve(UNUSED s32 arg0, u16 waypointIndex) {
    s16 thing = gCurrentTrackConsecutiveCurveCountsPath[waypointIndex];
    if (thing > 0) {
        return 1;
    }
    return 0;
}

bool is_far_from_path(s32 playerIndex) {
    f32 value = gTrackPositionFactor[playerIndex];
    if ((1.1f <= value) || (value <= -1.1f)) {
        return true;
    }
    return false;
}

f32 calculate_track_position_factor(f32 posX, f32 posZ, u16 waypointIndex, s32 pathIndex) {
    f32 x1;
    f32 z1;
    f32 x2;
    f32 z2;
    f32 squaredDistance;
    f32 math;
    TrackPathPoint* thing1;
    TrackPathPoint* thing2;

    thing1 = &gTrackLeftPaths[pathIndex][waypointIndex];
    thing2 = &gTrackRightPaths[pathIndex][waypointIndex];

    x1 = thing1->posX;
    z1 = thing1->posZ;
    x2 = thing2->posX;
    z2 = thing2->posZ;

    squaredDistance = ((x2 - x1) * (x2 - x1)) + ((z2 - z1) * (z2 - z1));
    if (squaredDistance < 0.01f) {
        return 0.0f;
    }
    math = ((2.0f * ((x2 - x1) * (posX - x1) + (z2 - z1) * (posZ - z1))) / squaredDistance) - 1.0f;
    return math;
}

void update_player_position_factor(s32 playerId, u16 waypointIndex, s32 pathIndex) {
    UNUSED Vec3f pad;
    gTrackPositionFactor[playerId] = 0.0f;
    if (CM_GetProps()->AIMaximumSeparation >= 0.0f) {
        if ((gPlayers[playerId].type & 0x8000) != 0) {
            gTrackPositionFactor[playerId] = calculate_track_position_factor(
                gPlayers[playerId].pos[0], gPlayers[playerId].pos[2], waypointIndex, pathIndex);
        }
    }
}

void calculate_track_offset_position(u16 waypointIndex, f32 arg1, f32 arg2, s16 pathIndex) {
    UNUSED s32 pad[4];
    f32 waypointOneX;
    f32 waypointOneZ;
    f32 waypointTwoX;
    f32 waypointTwoZ;
    UNUSED s32 pad2;
    f32 xdiff;
    f32 zdiff;
    f32 distance;
    UNUSED f32 temp_f12;
    UNUSED f32 temp_f2_2;
    UNUSED TrackPathPoint* path;
    TrackPathPoint* waypointTwo;
    TrackPathPoint* waypointOne;

    waypointOne = &gTrackPaths[pathIndex][waypointIndex];
    waypointOneX = waypointOne->posX;
    waypointOneZ = waypointOne->posZ;
    waypointTwo = &gTrackPaths[pathIndex][(waypointIndex + 1) % gSelectedPathCount];
    waypointTwoX = waypointTwo->posX;
    waypointTwoZ = waypointTwo->posZ;
    zdiff = waypointTwoZ - waypointOneZ;
    xdiff = waypointTwoX - waypointOneX;
    if (xdiff && xdiff) {}
    distance = sqrtf((xdiff * xdiff) + (zdiff * zdiff));
    if (distance < 0.01f) {
        gOffsetPosition[0] = waypointTwoX;
        gOffsetPosition[2] = waypointTwoZ;
    } else {
        gOffsetPosition[0] = ((0.5f - (arg1 * 0.5f)) * (((arg2 * zdiff) / distance) + waypointOneX)) +
                             ((1.0f - (0.5f - (arg1 * 0.5f))) * (((arg2 * -zdiff) / distance) + waypointOneX));
        gOffsetPosition[2] = ((0.5f - (arg1 * 0.5f)) * (((arg2 * -xdiff) / distance) + waypointOneZ)) +
                             ((1.0f - (0.5f - (arg1 * 0.5f))) * (((arg2 * xdiff) / distance) + waypointOneZ));
    }
}

void set_track_offset_position(u16 waypointIndex, f32 arg1, s16 pathIndex) {
    TrackPathPoint* path1;
    TrackPathPoint* path2;
    f32 x1;
    f32 z1;
    f32 x3;
    f32 z3;
    f32 x2;
    f32 z2;
    f32 x4;
    f32 z4;
    f32 temp_f0;
    f32 temp_f12;

    path1 = &gTrackLeftPaths[pathIndex][waypointIndex];
    path2 = &gTrackRightPaths[pathIndex][waypointIndex];

    x1 = (f32) path1->posX;
    z1 = (f32) path1->posZ;

    x2 = (f32) path2->posX;
    z2 = (f32) path2->posZ;

    waypointIndex += 1;
    waypointIndex = waypointIndex % gPathCountByPathIndex[pathIndex];

    path1 = &gTrackLeftPaths[pathIndex][waypointIndex];
    path2 = &gTrackRightPaths[pathIndex][waypointIndex];

    x3 = (f32) path1->posX;
    z3 = (f32) path1->posZ;

    x4 = (f32) path2->posX;
    z4 = (f32) path2->posZ;

    temp_f0 = 0.5f - (arg1 / 2.0f);
    temp_f12 = 1.0f - temp_f0;
    gOffsetPosition[0] = ((temp_f0 * (x1 + x3)) / 2.0f) + ((temp_f12 * (x2 + x4)) / 2.0f);
    gOffsetPosition[2] = ((temp_f0 * (z1 + z3)) / 2.0f) + ((temp_f12 * (z2 + z4)) / 2.0f);
}

s16 func_8000BD94(f32 posX, f32 posY, f32 posZ, s32 pathIndex) {
    f32 x_dist;
    f32 y_dist;
    f32 z_dist;
    f32 considerSquaredDistance;
    f32 minimumSquaredDistance;
    s32 considerWaypointIndex;
    s32 pathWaypointCount;
    s16 nearestWaypointIndex;
    TrackPathPoint* pathWaypoints;
    TrackPathPoint* considerWaypoint;

    pathWaypoints = gTrackPaths[pathIndex];
    pathWaypointCount = gPathCountByPathIndex[pathIndex];
    considerWaypoint = &pathWaypoints[0];
    x_dist = (f32) considerWaypoint->posX - posX;
    y_dist = (f32) considerWaypoint->posY - posY;
    z_dist = (f32) considerWaypoint->posZ - posZ;
    minimumSquaredDistance = (x_dist * x_dist) + (y_dist * y_dist) + (z_dist * z_dist);
    nearestWaypointIndex = 0;
    for (considerWaypointIndex = 1; considerWaypointIndex < pathWaypointCount;
         considerWaypoint++, considerWaypointIndex++) {
        x_dist = (f32) considerWaypoint->posX - posX;
        y_dist = (f32) considerWaypoint->posY - posY;
        z_dist = (f32) considerWaypoint->posZ - posZ;
        considerSquaredDistance = (x_dist * x_dist) + (y_dist * y_dist) + (z_dist * z_dist);
        if (considerSquaredDistance < minimumSquaredDistance) {
            nearestWaypointIndex = considerWaypointIndex;
            minimumSquaredDistance = considerSquaredDistance;
        }
    }
    return nearestWaypointIndex;
}

s16 find_closest_path_point_track_section(f32 posX, f32 posY, f32 posZ, u16 trackSectionId, s32* pathIndex) {
    TrackPathPoint* pathWaypoints;
    TrackPathPoint* considerWaypoint;
    f32 x_dist;
    f32 y_dist;
    f32 z_dist;
    f32 considerSquaredDistance;
    f32 minimumSquaredDistance;
    s32 considerWaypointIndex;
    s32 pathWaypointCount;
    s32 temp_t0;
    s32 var_a1;
    s32 var_t1;
    s32 considerPathIndex;
    s32 var_t4;
    s16 nearestWaypointIndex;

    minimumSquaredDistance = 1000000.0f;
    temp_t0 = *pathIndex;
    nearestWaypointIndex = 0;
    var_t1 = 0;
    var_a1 = 0;
    pathWaypoints = gTrackPaths[temp_t0];
    pathWaypointCount = gPathCountByPathIndex[temp_t0];
    considerWaypoint = &pathWaypoints[0];
    for (considerWaypointIndex = 0; considerWaypointIndex < pathWaypointCount;
         considerWaypointIndex++, considerWaypoint++) {
        if ((considerWaypoint->trackSectionId == trackSectionId) || (IsPodiumCeremony())) {
            var_t1 = 1;
            x_dist = (f32) considerWaypoint->posX - posX;
            y_dist = (f32) considerWaypoint->posY - posY;
            z_dist = (f32) considerWaypoint->posZ - posZ;
            considerSquaredDistance = (x_dist * x_dist) + (y_dist * y_dist) + (z_dist * z_dist);
            if (considerSquaredDistance < minimumSquaredDistance) {
                nearestWaypointIndex = considerWaypointIndex;
                var_a1 = 1;
                minimumSquaredDistance = considerSquaredDistance;
            }
        }
    }
    if (var_t1 == 0) {
        for (considerPathIndex = 0; considerPathIndex < 4; considerPathIndex++) {
            if ((considerPathIndex != temp_t0) && (D_80163368[considerPathIndex] >= 2)) {
                pathWaypoints = gTrackPaths[considerPathIndex];
                considerWaypoint = &pathWaypoints[0];
                pathWaypointCount = gPathCountByPathIndex[considerPathIndex];
                for (considerWaypointIndex = 0; considerWaypointIndex < pathWaypointCount;
                     considerWaypointIndex++, considerWaypoint++) {
                    if (considerWaypoint->trackSectionId == trackSectionId) {
                        x_dist = (f32) considerWaypoint->posX - posX;
                        y_dist = (f32) considerWaypoint->posY - posY;
                        z_dist = (f32) considerWaypoint->posZ - posZ;
                        considerSquaredDistance = (x_dist * x_dist) + (y_dist * y_dist) + (z_dist * z_dist);
                        if (considerSquaredDistance < minimumSquaredDistance) {
                            nearestWaypointIndex = considerWaypointIndex;
                            var_t4 = considerPathIndex;
                            var_a1 = 2;
                            minimumSquaredDistance = considerSquaredDistance;
                        }
                    }
                }
            }
        }
    }
    if (var_a1 == 0) {
        pathWaypoints = gTrackPaths[0];
        pathWaypointCount = gPathCountByPathIndex[0];
        considerWaypoint = &pathWaypoints[0];
        x_dist = (f32) considerWaypoint->posX - posX;
        y_dist = (f32) considerWaypoint->posY - posY;
        z_dist = (f32) considerWaypoint->posZ - posZ;
        minimumSquaredDistance = (x_dist * x_dist) + (y_dist * y_dist) + (z_dist * z_dist);
        nearestWaypointIndex = 0;
        for (considerWaypointIndex = 1; considerWaypointIndex < pathWaypointCount;
             considerWaypoint++, considerWaypointIndex++) {
            x_dist = (f32) considerWaypoint->posX - posX;
            y_dist = (f32) considerWaypoint->posY - posY;
            z_dist = (f32) considerWaypoint->posZ - posZ;
            considerSquaredDistance = (x_dist * x_dist) + (y_dist * y_dist) + (z_dist * z_dist);
            if (considerSquaredDistance < minimumSquaredDistance) {
                nearestWaypointIndex = considerWaypointIndex;
                var_t4 = 0;
                var_a1 = 2;
                minimumSquaredDistance = considerSquaredDistance;
            }
        }
    }
    if (var_a1 == 2) {
        *pathIndex = var_t4;
    }
    return nearestWaypointIndex;
}

/**
 * Tries to find the waypoint nearest to (posX, posY, posZ)
 * Only consider waypoints in the same segment as trackSectionId
 * Only considers waypoints within 500 units of(posX, posY, posZ)
 * Looks 3 waypoints behind and 6 waypoints ahead of waypointIndex
 **/
s16 update_path_index_with_track(f32 posX, f32 posY, f32 posZ, s16 waypointIndex, s32 pathIndex, u16 trackSectionId) {
    s16 nearestWaypointIndex;
    s16 searchIndex;
    s16 considerIndex;
    s32 pathWaypointCount;
    f32 x_dist;
    f32 y_dist;
    f32 z_dist;
    f32 minimumDistance;
    f32 squaredDistance;
    TrackPathPoint* pathWaypoints;
    TrackPathPoint* considerWaypoint;

    nearestWaypointIndex = -1;
    minimumDistance = 250000.0f;
    pathWaypointCount = gPathCountByPathIndex[pathIndex];
    pathWaypoints = gTrackPaths[pathIndex];
    for (searchIndex = waypointIndex - 3; searchIndex < waypointIndex + 7; searchIndex++) {
        // Its possible for searchIndex to be less than 0 or greater than the number of waypoints in a given path
        // This is done to ensure we access gTrackPaths at a valid index
        considerIndex = (searchIndex + pathWaypointCount) % pathWaypointCount;
        considerWaypoint = &pathWaypoints[considerIndex];
        if (considerWaypoint->trackSectionId == trackSectionId) {
            x_dist = considerWaypoint->posX - posX;
            y_dist = considerWaypoint->posY - posY;
            z_dist = considerWaypoint->posZ - posZ;
            squaredDistance = (x_dist * x_dist) + (y_dist * y_dist) + (z_dist * z_dist);
            if (squaredDistance < minimumDistance) {
                minimumDistance = squaredDistance;
                nearestWaypointIndex = considerIndex;
            }
        }
    }
    return nearestWaypointIndex;
}

/**
 * Tries to find the waypoint nearest to (posX, posY, posZ)
 * Only considers waypoints within 400 units of (posX, posY, posZ)
 * Looks 3 waypoints behind and 6 waypoints ahead of waypointIndex
 **/
s16 update_path_index(f32 posX, f32 posY, f32 posZ, s16 waypointIndex, s32 pathIndex) {
    s16 nearestWaypointIndex;
    s16 searchIndex;
    s16 considerIndex;
    s32 waypointFound;
    s32 pathWaypointCount;
    f32 x_dist;
    f32 y_dist;
    f32 z_dist;
    f32 minimumDistance;
    f32 squaredDistance;
    TrackPathPoint* pathWaypoints;
    TrackPathPoint* considerWaypoint;

    waypointFound = 0;
    nearestWaypointIndex = -1;
    minimumDistance = 160000.0f;
    pathWaypointCount = gPathCountByPathIndex[pathIndex];
    pathWaypoints = gTrackPaths[pathIndex];
    for (searchIndex = waypointIndex - 3; searchIndex < waypointIndex + 7; searchIndex++) {
        // Its possible for searchIndex to be less than 0 or greater than the number of waypoints in a given path
        // This is done to ensure we access gTrackPaths at a valid index
        considerIndex = (searchIndex + pathWaypointCount) % pathWaypointCount;
        considerWaypoint = &pathWaypoints[considerIndex];
        x_dist = considerWaypoint->posX - posX;
        y_dist = considerWaypoint->posY - posY;
        z_dist = considerWaypoint->posZ - posZ;
        squaredDistance = (x_dist * x_dist) + (y_dist * y_dist) + (z_dist * z_dist);
        if (squaredDistance < minimumDistance) {
            minimumDistance = squaredDistance;
            nearestWaypointIndex = considerIndex;
            waypointFound = 1;
        }
    }
    if (waypointFound == 0) {
        for (searchIndex = waypointIndex - 3; searchIndex < waypointIndex + 7; searchIndex++) {
            considerIndex = ((searchIndex + pathWaypointCount) % pathWaypointCount);
            considerWaypoint = &pathWaypoints[considerIndex];
            /**
             * This fake match is done to stop the compiler from optimzing out considerWaypoint.
             * Maybe if no waypoint was found some debugging info was printed out, but come
             * production time they removed the debug printing but not the loop?
             **/
            if (considerWaypoint && considerWaypoint) {};
        }
    }
    return nearestWaypointIndex;
}

void tweak_path_index_wario_stadium(UNUSED f32 posX, f32 posY, UNUSED f32 posZ, s16* waypointIndex, UNUSED s32 arg4) {
    s16 var_v0;

    var_v0 = *waypointIndex;
    if ((IsWarioStadium()) && (var_v0 >= 0x475) && (var_v0 < 0x480) && (posY < 0.0f)) {
        var_v0 = 0x0398;
    }
    *waypointIndex = var_v0;
}

void adjust_path_at_start_line(UNUSED f32 posX, UNUSED f32 posY, f32 posZ, s16* waypointIndex, s32 pathIndex) {
    s16 temp;
    temp = *waypointIndex;
    if (temp == 0) {
        if (gPathStartZ < posZ) {
            temp = gPathCountByPathIndex[pathIndex] - 1;
        }
    } else if (((temp + 1) == gPathCountByPathIndex[pathIndex]) && (posZ <= gPathStartZ)) {
        temp = 0;
    }
    *waypointIndex = temp;
}

s16 update_path_index_track_section(f32 posX, f32 posY, f32 posZ, Player* player, s32 playerId, s32* pathIndex) {
    u16 trackSectionId;
    s16 ret;

    trackSectionId = get_track_section_id(player->collision.meshIndexZX);
    if ((trackSectionId <= 0) || (trackSectionId >= 0x33)) {
        trackSectionId = gPlayersTrackSectionId[playerId];
    }
    gPlayersTrackSectionId[playerId] = trackSectionId;
    ret = find_closest_path_point_track_section(posX, posY, posZ, trackSectionId, pathIndex);
    gPathIndexByPlayerId[playerId] = *pathIndex;
    return ret;
}

s16 update_player_path(f32 posX, f32 posY, f32 posZ, s16 waypointIndex, Player* player, s32 playerId, s32 pathIndex) {
    s16 newWaypoint;
    UNUSED s16 stackPadding0;
    UNUSED s32 stackPadding1;
    UNUSED s32 stackPadding2;
    TrackPathPoint* temp_v1;

    if ((player->type & 0x4000) && !(player->type & 0x1000)) {
        newWaypoint = update_path_index_with_track(posX, posY, posZ, waypointIndex, pathIndex,
                                                   (u16) get_track_section_id(player->collision.meshIndexZX));
        if (newWaypoint == -1) {
            newWaypoint = update_path_index_track_section(posX, posY, posZ, player, playerId, &pathIndex);
        }
    } else {
        if (D_801631E0[playerId] == 1) {
            if (player->unk_0CA & 1) {
                temp_v1 = &gTrackPaths[pathIndex][waypointIndex];
                player->pos[0] = (f32) temp_v1->posX;
                player->pos[1] = (f32) temp_v1->posY;
                player->pos[2] = (f32) temp_v1->posZ;
                player->unk_0CA &= ~0x0001;
                return waypointIndex;
            }
            if (playerId == ((s32) D_80163488 % 8)) {
                check_bounding_collision(&player->collision, 10.0f, posX, posY, posZ);
                gPlayersTrackSectionId[playerId] = get_track_section_id(player->collision.meshIndexZX);
                newWaypoint = update_path_index_with_track(posX, posY, posZ, waypointIndex, pathIndex,
                                                           gPlayersTrackSectionId[playerId]);
                if (newWaypoint == -1) {
                    newWaypoint = update_path_index(posX, posY, posZ, waypointIndex, pathIndex);
                }
                if (newWaypoint == -1) {
                    newWaypoint = find_closest_path_point_track_section(posX, posY, posZ,
                                                                        gPlayersTrackSectionId[playerId], &pathIndex);
                    temp_v1 = &gTrackPaths[pathIndex][newWaypoint];
                    player->pos[0] = (f32) temp_v1->posX;
                    player->pos[1] = (f32) temp_v1->posY;
                    player->pos[2] = (f32) temp_v1->posZ;
                }
            } else {
                newWaypoint = update_path_index(posX, posY, posZ, waypointIndex, pathIndex);
                if (newWaypoint == -1) {
                    newWaypoint = func_8000BD94(posX, posY, posZ, pathIndex);
                    temp_v1 = &gTrackPaths[pathIndex][newWaypoint];
                    posX = (f32) temp_v1->posX;
                    posY = (f32) temp_v1->posY;
                    posZ = (f32) temp_v1->posZ;
                    player->pos[0] = posX;
                    player->pos[1] = posY;
                    player->pos[2] = posZ;
                    check_bounding_collision(&player->collision, 10.0f, posX, posY, posZ);
                    gPlayersTrackSectionId[playerId] = get_track_section_id(player->collision.meshIndexZX);
                }
            }
        } else {
            newWaypoint = update_path_index(posX, posY, posZ, waypointIndex, pathIndex);
            if (newWaypoint == -1) {
                newWaypoint = update_path_index_track_section(posX, posY, posZ, player, playerId, &pathIndex);
            }
        }
        tweak_path_index_wario_stadium(posX, posY, posZ, &newWaypoint, pathIndex);
    }
    adjust_path_at_start_line(posX, posY, posZ, &newWaypoint, pathIndex);
    return newWaypoint;
}

s16 find_closest_vehicles_path_point(f32 xPos, UNUSED f32 yPos, f32 zPos, s16 waypointIndex) {
    f32 xdiff;
    f32 zdiff;
    f32 minimumDistance;
    f32 considerSquaredDistance;
    s16 realIndex;
    s16 minimumIndex;
    s16 considerIndex;
    Path2D* considerWaypoint;

    minimumDistance = 250000.0f;
    minimumIndex = -1;
    for (realIndex = waypointIndex - 2; realIndex < waypointIndex + 7; realIndex++) {
        considerIndex = realIndex;
        if (realIndex < 0) {
            considerIndex = realIndex + gVehicle2DPathLength;
        }
        considerIndex %= gVehicle2DPathLength;
        considerWaypoint = &gVehicle2DPathPoint[considerIndex];
        xdiff = considerWaypoint->x - xPos;
        zdiff = considerWaypoint->z - zPos;
        considerSquaredDistance = (xdiff * xdiff) + (zdiff * zdiff);
        if (considerSquaredDistance < minimumDistance) {
            minimumDistance = considerSquaredDistance;
            minimumIndex = considerIndex;
        }
    }
    if (minimumIndex == -1) {
        minimumIndex = waypointIndex;
    }
    return minimumIndex;
}

s16 func_8000D24C(f32 posX, f32 posY, f32 posZ, s32* pathIndex) {
    UNUSED s32 pad;
    Collision sp24;

    check_bounding_collision(&sp24, 10.0f, posX, posY, posZ);
    return find_closest_path_point_track_section(posX, posY, posZ, get_track_section_id(sp24.meshIndexZX), pathIndex);
}

s16 func_8000D2B4(f32 posX, f32 posY, f32 posZ, s16 waypointIndex, s32 pathIndex) {
    s16 waypoint;

    waypoint = update_path_index(posX, posY, posZ, waypointIndex, pathIndex);
    if (waypoint == -1) {
        waypoint = func_8000D24C(posX, posY, posZ, &pathIndex);
    }
    adjust_path_at_start_line(posX, posY, posZ, &waypoint, pathIndex);
    return waypoint;
}

s16 func_8000D33C(f32 posX, f32 posY, f32 posZ, s16 waypointIndex, s32 pathIndex) {
    s16 waypoint;

    waypoint = update_path_index(posX, posY, posZ, waypointIndex, pathIndex);
    if (waypoint == -1) {
        waypoint = func_8000D24C(posX, posY, posZ, &pathIndex);
    }
    return waypoint;
}

f32 cpu_track_position_factor(s32 playerId) {
    TrackPositionFactorInstruction* temp_v0;
    f32 target;
    f32 current;

    temp_v0 = &gPlayerTrackPositionFactorInstruction[playerId];
    current = temp_v0->current;
    target = temp_v0->target;
    if (current < target) {
        current += temp_v0->step;
        if (target < current) {
            current = target;
        }
    } else if (target < current) {
        current -= temp_v0->step;
        if (current < target) {
            current = target;
        }
    }

    temp_v0->current = current;
    return current;
}

void determine_ideal_cpu_position_offset(s32 arg0, u16 arg1) {
    UNUSED s32 stackPadding0;
    f32 sp48;
    f32 sp44;
    UNUSED s32 stackPadding1;
    UNUSED s32 stackPadding2;
    UNUSED s32 stackPadding3;
    f32 stackPadding4;
    f32 stackPadding5;
    f32 sp2C;
    s32 var_a2;
    s16 temp_v1;
    u16 thing;

    temp_v1 = gCurrentTrackConsecutiveCurveCountsPath[arg1];
    var_a2 = 6;
    sp2C = cpu_track_position_factor(arg0);
    thing = arg1;

    if (IsPodiumCeremony()) {
        var_a2 = 1;
    } else if (IsToadsTurnpike()) {
        var_a2 = 7;
    } else if (IsYoshiValley()) {
    } else {
        if (temp_v1 < 6) {
            var_a2 = 8;
        } else if (temp_v1 >= 0x15) {
            var_a2 = 20;
        }
    }

    if (var_a2 >= 8) {
        if ((gTrackPositionFactor[arg0] > 0.75f) && (gCurrentTrackSectionTypesPath[thing] == 0)) {
            var_a2 = 7;
        }
        if ((gTrackPositionFactor[arg0] < -0.75f) && (gCurrentTrackSectionTypesPath[thing] == 1)) {
            var_a2 = 7;
        }
    }
    if (is_far_from_path(arg0) == 1) {
        var_a2 = 5;
    }
    if (gCurrentPlayerLookAhead[arg0] < var_a2) {
        gCurrentPlayerLookAhead[arg0]++;
    }
    if (var_a2 < gCurrentPlayerLookAhead[arg0]) {
        gCurrentPlayerLookAhead[arg0]--;
    }
    arg1 = (gCurrentPlayerLookAhead[arg0] + arg1) % gSelectedPathCount;
    set_track_offset_position(arg1, sp2C, gPlayerPathIndex);
    sp48 = gOffsetPosition[0];
    sp44 = gOffsetPosition[2];
    set_track_offset_position(((arg1 + 1) % gSelectedPathCount) & 0xFFFF, sp2C, gPlayerPathIndex);
    stackPadding5 = gOffsetPosition[0];
    gOffsetPosition[0] = (sp48 + stackPadding5) * 0.5f;
    stackPadding4 = gOffsetPosition[2];
    gOffsetPosition[2] = (sp44 + stackPadding4) * 0.5f;
}

s16 func_8000D6D0(Vec3f position, s16* waypointIndex, f32 speed, f32 arg3, s16 pathIndex, s16 arg5) {
    f32 temp1;
    f32 temp2;
    f32 midX;
    UNUSED s16 stackPadding1;
    s16 waypoint1;
    s16 waypoint2;
    f32 pad3;
    f32 midY;
    f32 pad4;
    f32 midZ;
    f32 distance;
    f32 oldPosX;
    f32 oldPosY;
    f32 oldPosZ;
    f32 var_f2;
    f32 var_f12;
    f32 var_f14;
    s16 temp_v0;
    s32 temp_v1;
    f32 xdiff;
    f32 ydiff;
    f32 zdiff;
    Vec3f oldPos;
    TrackPathPoint* path;

    path = gTrackPaths[pathIndex];
    oldPos[0] = position[0];
    oldPos[1] = position[1];
    oldPos[2] = position[2];
    oldPosX = position[0];
    oldPosY = position[1];
    oldPosZ = position[2];
    temp_v0 = func_8000D2B4(oldPosX, oldPosY, oldPosZ, *waypointIndex, (s32) pathIndex);
    *waypointIndex = temp_v0;
    temp_v1 = temp_v0 + arg5;
    waypoint1 = temp_v1 % gPathCountByPathIndex[pathIndex];
    waypoint2 = (temp_v1 + 1) % gPathCountByPathIndex[pathIndex];
    set_track_offset_position(waypoint1, arg3, pathIndex);
    pad3 = gOffsetPosition[0];
    pad4 = gOffsetPosition[2];
    set_track_offset_position(waypoint2, arg3, pathIndex);
    temp1 = gOffsetPosition[0];
    temp2 = gOffsetPosition[2];
    midY = (path[waypoint1].posY + path[waypoint2].posY) * 0.5f;
    midX = (pad3 + temp1) * 0.5f;
    midZ = (pad4 + temp2) * 0.5f;
    xdiff = midX - oldPosX;
    ydiff = midY - oldPosY;
    zdiff = midZ - oldPosZ;
    distance = sqrtf((xdiff * xdiff) + (ydiff * ydiff) + (zdiff * zdiff));
    if (distance > 0.01f) {
        var_f2 = ((xdiff * speed) / distance) + oldPosX;
        var_f12 = ((ydiff * speed) / distance) + oldPosY;
        var_f14 = ((zdiff * speed) / distance) + oldPosZ;
    } else {
        var_f2 = oldPosX;
        var_f12 = oldPosY;
        var_f14 = oldPosZ;
    }
    position[0] = var_f2;
    position[1] = var_f12;
    position[2] = var_f14;
    return get_angle_between_path(oldPos, position);
}

s16 func_8000D940(Vec3f pos, s16* waypointIndex, f32 speed, f32 arg3, s16 pathIndex) {
    UNUSED f32 pad;
    f32 thing1;
    f32 thing2;
    UNUSED s16 stackPadding1;
    s16 waypoint1;
    s16 waypoint2;
    UNUSED s16 stackPadding2;
    f32 pad2;
    f32 midX;
    f32 pad3;
    f32 midY;
    f32 midZ;
    f32 distance;
    f32 temp_f20;
    f32 temp_f22;
    f32 temp_f24;
    f32 var_f2;
    f32 var_f12;
    f32 var_f14;
    s16 temp_v0;
    f32 xdiff;
    f32 ydiff;
    f32 zdiff;
    s32 waypointCount;
    Vec3f sp54;

    sp54[0] = pos[0];
    sp54[1] = pos[1];
    sp54[2] = pos[2];
    waypointCount = gPathCountByPathIndex[pathIndex];
    temp_f20 = pos[0];
    temp_f22 = pos[1];
    temp_f24 = pos[2];
    temp_v0 = func_8000D2B4(temp_f20, temp_f22, temp_f24, *waypointIndex, (s32) pathIndex);
    *waypointIndex = temp_v0;
    waypoint1 = ((temp_v0 + waypointCount) - 3) % waypointCount;
    waypoint2 = ((temp_v0 + waypointCount) - 4) % waypointCount;
    set_track_offset_position(waypoint1, arg3, pathIndex);
    pad2 = gOffsetPosition[0];
    pad3 = gOffsetPosition[2];
    set_track_offset_position(waypoint2, arg3, pathIndex);
    thing1 = gOffsetPosition[0];
    thing2 = gOffsetPosition[2];
    midY = (gTrackPaths[pathIndex][waypoint1].posY + gTrackPaths[pathIndex][waypoint2].posY) * 0.5f;
    midX = (pad2 + thing1) * 0.5f;
    midZ = (pad3 + thing2) * 0.5f;
    xdiff = midX - temp_f20;
    ydiff = midY - temp_f22;
    zdiff = midZ - temp_f24;
    distance = sqrtf((xdiff * xdiff) + (ydiff * ydiff) + (zdiff * zdiff));
    if (distance > 0.01f) {
        var_f2 = ((xdiff * speed) / distance) + temp_f20;
        var_f12 = ((ydiff * speed) / distance) + temp_f22;
        var_f14 = ((zdiff * speed) / distance) + temp_f24;
    } else {
        var_f2 = temp_f20;
        var_f12 = temp_f22;
        var_f14 = temp_f24;
    }
    pos[0] = var_f2;
    pos[1] = var_f12;
    pos[2] = var_f14;
    return get_angle_between_path(sp54, pos);
}

s16 update_vehicle_following_path(Vec3f pos, s16* waypointIndex, f32 speed) {
    f32 origXPos;
    f32 origYPos;
    f32 origZPos;
    UNUSED s32 stackPadding0;
    UNUSED s32 stackPadding1;
    UNUSED s32 stackPadding2;
    UNUSED s32 stackPadding3;
    UNUSED s32 stackPadding4;
    UNUSED s32 stackPadding5;
    UNUSED s32 stackPadding6;
    UNUSED s32 stackPadding7;
    UNUSED s32 stackPadding8;
    f32 farWaypointAverageX;
    f32 farWaypointAverageZ;
    f32 x_dist;
    f32 y_dist;
    f32 distance;
    f32 newX;
    f32 newZ;
    s16 newWaypointIndex;
    s16 farWaypoint1;
    s16 farWaypoint2;
    Path2D* temp_a0;
    Path2D* temp_a2;
    Vec3f sp38;

    origXPos = pos[0];
    origYPos = pos[1];
    origZPos = pos[2];
    sp38[0] = pos[0];
    sp38[1] = pos[1];
    sp38[2] = pos[2];
    newWaypointIndex = find_closest_vehicles_path_point(origXPos, origYPos, origZPos, *waypointIndex);
    *waypointIndex = newWaypointIndex;
    farWaypoint1 = (newWaypointIndex + 3) % gVehicle2DPathLength;
    farWaypoint2 = (newWaypointIndex + 4) % gVehicle2DPathLength;
    temp_a0 = &gVehicle2DPathPoint[farWaypoint1];
    temp_a2 = &gVehicle2DPathPoint[farWaypoint2];
    farWaypointAverageX = (temp_a0->x + temp_a2->x) * 0.5f;
    farWaypointAverageZ = (temp_a0->z + temp_a2->z) * 0.5f;
    x_dist = farWaypointAverageX - origXPos;
    y_dist = farWaypointAverageZ - origZPos;
    distance = sqrtf((x_dist * x_dist) + (y_dist * y_dist));
    if (distance > 0.01f) {
        newX = ((x_dist * speed) / distance) + origXPos;
        newZ = ((y_dist * speed) / distance) + origZPos;
    } else {
        newX = origXPos;
        newZ = origZPos;
    }
    pos[0] = newX;
    pos[1] = origYPos;
    pos[2] = newZ;
    return get_angle_between_path(sp38, pos);
}

void set_bomb_kart_spawn_positions(void) {
    UNUSED Collision* var_s2;
    f32 startingXPos;
    f32 startingZPos;
    f32 startingYPos;
    s32 var_s3;
    TrackPathPoint* temp_v0;
    UNUSED BombKart* var_s0;
    BombKartSpawn* bombKartSpawn;

    for (var_s3 = 0; var_s3 < NUM_BOMB_KARTS_VERSUS; var_s3++) {
        // bombKartSpawn = &gBombKartSpawns[gCurrentCourseId][var_s3];
        if (IsYoshiValley()) {
            startingXPos = bombKartSpawn->startingXPos;
            startingZPos = bombKartSpawn->startingZPos;
            startingYPos = spawn_actor_on_surface(startingXPos, 2000.0f, startingZPos);
        } else if (IsPodiumCeremony()) {
            temp_v0 = &gTrackPaths[3][bombKartSpawn->waypointIndex];
            startingXPos = temp_v0->posX;
            startingYPos = temp_v0->posY;
            startingZPos = temp_v0->posZ;
        } else {
            temp_v0 = &gTrackPaths[0][bombKartSpawn->waypointIndex];
            startingXPos = temp_v0->posX;
            startingYPos = temp_v0->posY;
            startingZPos = temp_v0->posZ;
        }

        gBombKarts[var_s3].bombPos[0] = startingXPos;
        gBombKarts[var_s3].bombPos[1] = startingYPos;
        gBombKarts[var_s3].bombPos[2] = startingZPos;
        gBombKarts[var_s3].wheel1Pos[0] = startingXPos;
        gBombKarts[var_s3].wheel1Pos[1] = startingYPos;
        gBombKarts[var_s3].wheel1Pos[2] = startingZPos;
        gBombKarts[var_s3].wheel2Pos[0] = startingXPos;
        gBombKarts[var_s3].wheel2Pos[1] = startingYPos;
        gBombKarts[var_s3].wheel2Pos[2] = startingZPos;
        gBombKarts[var_s3].wheel3Pos[0] = startingXPos;
        gBombKarts[var_s3].wheel3Pos[1] = startingYPos;
        gBombKarts[var_s3].wheel3Pos[2] = startingZPos;
        gBombKarts[var_s3].wheel4Pos[0] = startingXPos;
        gBombKarts[var_s3].wheel4Pos[1] = startingYPos;
        gBombKarts[var_s3].wheel4Pos[2] = startingZPos;
        gBombKarts[var_s3].waypointIndex = bombKartSpawn->waypointIndex;
        gBombKarts[var_s3].unk_3C = bombKartSpawn->unk_04;
        gBombKarts[var_s3].bounceTimer = 0;
        gBombKarts[var_s3].circleTimer = 0;
        gBombKarts[var_s3].state = bombKartSpawn->startingState;
        gBombKarts[var_s3].unk_4A = 0;
        gBombKarts[var_s3].unk_4C = 1;
        gBombKarts[var_s3].yPos = startingYPos;
        check_bounding_collision(&gBombKartCollision[var_s3], 2.0f, startingXPos, startingYPos, startingZPos);
    }
}

void func_8000DF8C(s32 bombKartId) {
    UNUSED s32 stackPadding0;
    f32 sp118;
    f32 var_f18;
    TrackPathPoint* temp_v0_2;
    f32 temp_f0_3;
    f32 sp108;
    UNUSED s32 stackPadding1;
    UNUSED s32 stackPadding2;
    UNUSED s32 stackPadding3;
    f32 temp_f14;
    UNUSED s32 stackPadding4;
    f32 temp_f16;
    UNUSED s32 stackPadding5;
    UNUSED s32 stackPadding6;
    UNUSED s32 stackPadding7;
    UNUSED s32 stackPadding8;
    UNUSED s32 stackPadding9;
    UNUSED s32 stackPaddingA;
    UNUSED s32 stackPaddingB;
    f32 temp_f0;
    f32 temp_f0_4;
    u16 spCA;
    f32 spC4;
    u16 spC2;
    f32 temp_f12;
    f32 temp_f12_3;
    f32 temp_f12_4;
    f32 temp_f14_2;
    f32 spAC;
    UNUSED s32 stackPaddingC;
    f32 temp_f16_2;
    f32 spA0;
    f32 temp_f2;
    f32 temp_f2_4;
    f32 sp94;
    UNUSED s32 stackPaddingD;
    f32 var_f20;
    f32 sp88;
    f32 var_f22;
    f32 var_f24;
    u16 sp7E;
    u16 sp7C;
    UNUSED u16 sp4C;
    u16 temp_t6;
    u16 temp_t7;
    u16 var_s1;
    s32 var_a0;
    UNUSED s32 stackPaddingE;
    TrackPathPoint* temp_v0_4;
    BombKart* bombKart;
    BombKart* bombKart2;
    Collision* temp_a0_4;
    Player* var_v0;

    bombKart = &gBombKarts[bombKartId];

    sp7E = bombKart->state;

    if (sp7E == 0) {
        return;
    }

    if (((bombKart->unk_4A != 1) || (IsPodiumCeremony()))) {
        var_f22 = bombKart->bombPos[0];
        var_f20 = bombKart->bombPos[1];
        var_f24 = bombKart->bombPos[2];
        spCA = bombKart->waypointIndex;
        spC4 = bombKart->unk_3C;
        spC2 = bombKart->someRot;
        sp7C = bombKart->bounceTimer;
        var_s1 = bombKart->circleTimer;
        if ((sp7E != 0) && (sp7E != 4)) {
            if (1) {}
            if (IsPodiumCeremony()) {
                if (D_8016347E == 1) {
                    var_v0 = gPlayerFour;
                    temp_f0 = var_f22 - var_v0->pos[0];
                    temp_f2 = var_f20 - var_v0->pos[1];
                    temp_f12 = var_f24 - var_v0->pos[2];
                    if ((((temp_f0 * temp_f0) + (temp_f2 * temp_f2)) + (temp_f12 * temp_f12)) < 25.0f) {
                        var_s1 = 0;
                        sp7E = 4;
                        var_v0->soundEffects |= 0x400000;
                        var_v0->type &= ~0x2000;
                    }
                }
            } else {

                for (var_a0 = 0; var_a0 < gPlayerCount; var_a0++) {
                    var_v0 = &gPlayers[var_a0];
                    if (!(var_v0->effects & 0x80000000)) {
                        temp_f0 = var_f22 - var_v0->pos[0];
                        temp_f2 = var_f20 - var_v0->pos[1];
                        temp_f12 = var_f24 - var_v0->pos[2];
                        if ((((temp_f0 * temp_f0) + (temp_f2 * temp_f2)) + (temp_f12 * temp_f12)) < 25.0f) {
                            sp7E = 4;
                            var_s1 = 0;
                            if (IsFrappeSnowland()) {
                                var_v0->soundEffects |= 0x01000000;
                            } else {
                                var_v0->soundEffects |= 0x400000;
                            }
                        }
                    }
                }
            }
        }
        switch (sp7E) {
            case 1:
                var_s1 = (var_s1 + 356) % 360;
                temp_t6 = (var_s1 * 0xFFFF) / 360;
                sp118 = coss(temp_t6) * 25.0;
                temp_f0_3 = sins(temp_t6) * 25.0;
                temp_v0_2 = &gTrackPaths[0][spCA];
                var_f22 = temp_v0_2->posX + sp118;
                var_f20 = bombKart->yPos + 3.5f;
                var_f24 = temp_v0_2->posZ + temp_f0_3;
                D_80162FB0[0] = var_f22;
                D_80162FB0[1] = var_f20;
                D_80162FB0[2] = var_f24;
                temp_t7 = (((var_s1 + 1) % 360) * 0xFFFF) / 360;
                sp118 = coss(temp_t7) * 25.0;
                temp_f0_3 = sins(temp_t7) * 25.0;
                D_80162FC0[0] = temp_v0_2->posX + sp118;
                D_80162FC0[1] = temp_v0_2->posY;
                D_80162FC0[2] = temp_v0_2->posZ + temp_f0_3;
                spC2 = (get_angle_between_two_vectors(D_80162FB0, D_80162FC0) * 0xFFFF) / 65520;
                break;
            case 2:
                var_s1 = (var_s1 + 4) % 360;
                temp_t6 = (var_s1 * 0xFFFF) / 360;
                sp118 = coss(temp_t6) * 25.0;
                temp_f0_3 = sins(temp_t6) * 25.0;
                temp_v0_2 = &gTrackPaths[0][spCA];
                var_f22 = temp_v0_2->posX + sp118;
                var_f20 = bombKart->yPos + 3.5f;
                var_f24 = temp_v0_2->posZ + temp_f0_3;
                D_80162FB0[0] = var_f22;
                D_80162FB0[1] = var_f20;
                D_80162FB0[2] = var_f24;
                temp_t7 = (((var_s1 + 1) % 360) * 0xFFFF) / 360;
                sp118 = coss(temp_t7) * 25.0;
                temp_f0_3 = sins(temp_t7) * 25.0;
                D_80162FC0[0] = temp_v0_2->posX + sp118;
                D_80162FC0[1] = temp_v0_2->posY;
                D_80162FC0[2] = temp_v0_2->posZ + temp_f0_3;
                spC2 = (get_angle_between_two_vectors(D_80162FB0, D_80162FC0) * 0xFFFF) / 65520;
                break;
            case 3:
                var_f20 = bombKart->yPos + 3.5f;
                spC2 = 0;
                break;

            case 5:
                if ((D_8016347C == 0) || (gNearestPathPointByPlayerId[3] < 5)) {
                    break;
                } else {
                    spCA = func_8000D2B4(var_f22, var_f20, var_f24, spCA, 3);
                    if ((spCA < 0) || (gPathCountByPathIndex[3] < spCA)) {
                        spCA = 0;
                    }
                    if (((s32) spCA) < 0x1A) {
                        temp_v0_2 = &gTrackPaths[3][(spCA + 1) % gPathCountByPathIndex[3]];
                        D_80162FB0[0] = temp_v0_2->posX;
                        D_80162FB0[1] = temp_v0_2->posY;
                        D_80162FB0[2] = temp_v0_2->posZ;
                        temp_v0_4 = &gTrackPaths[3][(spCA + 2) % gPathCountByPathIndex[3]];
                        D_80162FC0[0] = temp_v0_4->posX;
                        D_80162FC0[1] = temp_v0_4->posY;
                        D_80162FC0[2] = temp_v0_4->posZ;
                        spC2 = (get_angle_between_two_vectors(D_80162FB0, D_80162FC0) * 0xFFFF) / 65520;
                    } else {
                        D_80162FB0[0] = var_f22;
                        D_80162FB0[1] = var_f20;
                        D_80162FB0[2] = var_f24;
                        D_80162FC0[0] = -2409.197f;
                        D_80162FC0[1] = 0.0f;
                        D_80162FC0[2] = -355.254f;
                        spC2 = (get_angle_between_two_vectors(D_80162FB0, D_80162FC0) * 0xFFFF) / 65520;
                    }
                    temp_f14 = ((D_80162FB0[0] + D_80162FC0[0]) * 0.5f) - var_f22;
                    temp_f16 = ((D_80162FB0[2] + D_80162FC0[2]) * 0.5f) - var_f24;
                    temp_f0_4 = sqrtf((temp_f14 * temp_f14) + (temp_f16 * temp_f16));
                    if (temp_f0_4 > 0.01f) {
                        var_f22 += (bombKart->unk_3C * temp_f14) / temp_f0_4;
                        var_f24 += (bombKart->unk_3C * temp_f16) / temp_f0_4;
                    } else {
                        var_f22 += temp_f14 / 5.0f;
                        var_f24 += temp_f16 / 5.0f;
                    }
                    temp_a0_4 = &gBombKartCollision[bombKartId];
                    var_f20 = calculate_surface_height(var_f22, 2000.0f, var_f24, temp_a0_4->meshIndexZX) + 3.5f;
                    if (var_f20 < (-1000.0)) {
                        var_f20 = bombKart->bombPos[1];
                    }
                    check_bounding_collision(temp_a0_4, 10.0f, var_f22, var_f20, var_f24);
                }
                break;
            case 4:
                temp_v0_2 = &gTrackPaths[0][spCA];
                D_80162FB0[0] = temp_v0_2->posX;
                D_80162FB0[1] = temp_v0_2->posY;
                D_80162FB0[2] = temp_v0_2->posZ;
                temp_v0_4 = &gTrackPaths[0][(spCA + 1) % gPathCountByPathIndex[0]];
                D_80162FC0[0] = temp_v0_4->posX;
                D_80162FC0[1] = temp_v0_4->posY;
                D_80162FC0[2] = temp_v0_4->posZ;
                var_f20 += 3.0f - (var_s1 * 0.3f);
                spC2 = (get_angle_between_two_vectors(D_80162FB0, D_80162FC0) * 0xFFFF) / 65520;
                break;
            default:
                break;
        }

        if (sp7E == 4) {
            sp108 = 2.0f * var_s1;
            sp118 = coss(0xFFFF - spC2) * var_s1;
            var_f18 = sins(0xFFFF - spC2) * var_s1;
            var_s1++;
            temp_f2_4 = (var_f20 - 2.3f) + (sp108 / 3.0f);
            spAC = temp_f2_4;
            spA0 = temp_f2_4;
            sp94 = temp_f2_4;
            sp88 = temp_f2_4;
            if (var_s1 >= 31) {
                sp7E = 0;
            }
        } else {
            sp118 = coss(0xFFFF - spC2) * 1.5f;
            var_f18 = sins(0xFFFF - spC2) * 1.5f;
            temp_f16_2 = var_f20 - 2.3f;
            temp_f12_3 = (sp7C % 3) * 0.15f;
            temp_f14_2 = temp_f16_2 - temp_f12_3;
            temp_f12_4 = temp_f16_2 + temp_f12_3;
            spAC = temp_f14_2;
            sp94 = temp_f14_2;
            spA0 = temp_f12_4;
            sp88 = temp_f12_4;
            var_f20 += sins((sp7C * 0x13FFEC) / 360);
            sp7C = (sp7C + 1) % 18;
        }
        bombKart2 = bombKart;
        bombKart2->wheel1Pos[0] = (sp118 - var_f18) + var_f22;
        bombKart2->wheel1Pos[1] = spAC;
        bombKart2->wheel1Pos[2] = (var_f18 + sp118) + var_f24;
        bombKart2->wheel2Pos[0] = (var_f18 + sp118) + var_f22;
        bombKart2->wheel2Pos[1] = spA0;
        bombKart2->wheel2Pos[2] = (var_f18 - sp118) + var_f24;
        bombKart2->wheel3Pos[0] = ((-sp118) - var_f18) + var_f22;
        bombKart2->wheel3Pos[1] = sp94;
        bombKart2->wheel3Pos[2] = ((-var_f18) + sp118) + var_f24;
        bombKart2->wheel4Pos[0] = ((-sp118) + var_f18) + var_f22;
        bombKart2->wheel4Pos[1] = sp88;
        bombKart2->wheel4Pos[2] = ((-var_f18) - sp118) + var_f24;
        bombKart2->bombPos[0] = var_f22;
        bombKart2->bombPos[1] = var_f20;
        bombKart2->bombPos[2] = var_f24;
        bombKart2->waypointIndex = spCA;
        bombKart2->unk_3C = spC4;
        bombKart2->someRot = spC2;
        bombKart2->state = sp7E;
        bombKart2->bounceTimer = sp7C;
        bombKart2->circleTimer = var_s1;
    }
}

s32 add_actor_in_unexpired_actor_list(s32 actorIndex, s16 arg1) {
    s32 i;
    s32 a2 = 0;

    for (i = 0; i < NUM_PLAYERS; i++) {

        if (gUnexpiredActorsList[i].unkC == 0) {
            gUnexpiredActorsList[i].unkC = 1;
            gUnexpiredActorsList[i].actorIndex = actorIndex;
            gUnexpiredActorsList[i].unk10 = arg1;
            gUnexpiredActorsList[i].unk14 = 0;
            a2 = 1;
            break;
        }
    }
    if (a2 == 0) {
        return -2;
    }
    return 0;
}

s32 add_red_shell_in_unexpired_actor_list(s32 actorIndex) {
    struct Actor* actor = CM_GetActor(actorIndex);
    if (actor->type != ACTOR_RED_SHELL) {
        return -1;
    }
    return add_actor_in_unexpired_actor_list(actorIndex, 0);
}

s32 add_green_shell_in_unexpired_actor_list(s32 actorIndex) {
    struct Actor* actor = CM_GetActor(actorIndex);
    if (actor->type != ACTOR_GREEN_SHELL) {
        return -1;
    }
    return add_actor_in_unexpired_actor_list(actorIndex, 1);
}

s32 add_blue_shell_in_unexpired_actor_list(s32 arg0) {
    struct Actor* actor = CM_GetActor(arg0);
    if (actor->type != ACTOR_BLUE_SPINY_SHELL) {
        return -1;
    }
    return add_actor_in_unexpired_actor_list(arg0, 2);
}

void delete_actor_in_unexpired_actor_list(s32 actorIndex) {
    struct unexpiredActors* phi;
    s32 i;

    for (i = 0; i < NUM_PLAYERS; i++) {
        phi = &gUnexpiredActorsList[i];
        if (actorIndex == phi->actorIndex) {
            phi->unkC = 0;
            phi->actorIndex = 1000; // out of bounds
        }
    }
}

void func_8000EEDC(void) {
    struct unexpiredActors* phi;
    s32 i;

    for (i = 0; i < NUM_PLAYERS; i++) {
        phi = &gUnexpiredActorsList[i];
        phi->unkC = 0;
        phi->actorIndex = 1000; // out of bounds
    }
}

void generate_player_smoke(void) {
    s32 someIndex;
    f32 var_f20;
    struct Actor* temp_s1;
    struct unexpiredActors* var_s0;

    for (someIndex = 0; someIndex < NUM_PLAYERS; someIndex++) {
        var_s0 = &gUnexpiredActorsList[someIndex];
        if (var_s0->unkC == 1) {
            temp_s1 = GET_ACTOR(var_s0->actorIndex);
            var_s0->unk14++;
            switch (var_s0->unk10) {
                case 0:
                    if (var_s0->unk14 < 0xA) {
                        var_f20 = 0.3f;
                    } else {
                        var_f20 = 0.9f;
                    }
                    break;
                case 1:
                    if (var_s0->unk14 < 0xA) {
                        var_f20 = 0.15f;
                    } else {
                        var_f20 = 0.45f;
                    }
                    break;
                case 2:
                    if (var_s0->unk14 < 0xA) {
                        var_f20 = 0.15f;
                    } else {
                        var_f20 = 0.45f;
                    }
                    break;
                default:
                    var_f20 = 1.0f;
                    break;
            }
            if (!(var_s0->unk14 & 1)) {
                init_smoke_particle(temp_s1->pos, ((random_int(30) + 20) * var_f20) / 50.0f, var_s0->unk10);
            }
        }
    }
}

void func_8000F0E0(void) {
    s32 i;
    for (i = 0; i < 4; i++) {
        D_80164670[i] = 0;
        D_80164678[i] = 0;
    }
}

void func_8000F124(void) {
    s32 var_a1;
    s32 i, j;

    for (j = 0; j < 2; j++) {
        D_80163348[j] = 0;
    }

    if (gDemoMode == 1) {
        return;
    }
    if (gModeSelection != GRAND_PRIX) {
        return;
    }

    for (i = 0; i < 2; i++) {

        while (1) {
            D_80163348[i] = random_int(8);

            if (gPlayerCount > 2) {
                break;
            }
            if (gPlayerCount < 1) {
                break;
            }

            var_a1 = 0;

            for (j = 0; j < gPlayerCount; j++) {
                if (gCharacterSelections[j] == D_80163348[i]) {
                    var_a1 = 1;
                }
            }
            for (j = 0; j < i; j++) {
                if (D_80163348[j] == D_80163348[i]) {
                    var_a1 = 1;
                }
            }
            if (var_a1 == 0) {
                break;
            }
        }
    }
}

// Delete track waypoints
void clear_path_point(TrackPathPoint* arg0, size_t size) {
    bzero((void*) arg0, size * sizeof(TrackPathPoint));
}

// Appears to allocate memory for each course.
void init_course_path_point(void) {

    _struct_gCoursePathSizes_0x10* ptr = &CM_GetProps()->PathSizes;
    s32 temp;
    s32 i;

    D_80163368[0] = (s32) ptr->unk0;
    D_80163368[1] = (s32) ptr->unk2;
    D_80163368[2] = (s32) ptr->unk4;
    D_80163368[3] = (s32) ptr->unk6;

    temp = ptr->unk8;
    gVehicle2DPathPoint = get_next_available_memory_addr(temp * 4);

    // Podium ceremony appears to allocate 1 * 8 bytes of data. Which would be aligned to 0x10.
    for (i = 0; i < 4; i++) {
        gTrackPaths[i] = get_next_available_memory_addr(D_80163368[i] * 8);
        gTrackLeftPaths[i] = get_next_available_memory_addr(D_80163368[i] * 8);
        gTrackRightPaths[i] = get_next_available_memory_addr(D_80163368[i] * 8);
        gTrackSectionTypes[i] = get_next_available_memory_addr(D_80163368[i] * 2);
        gPathExpectedRotation[i] = get_next_available_memory_addr(D_80163368[i] * 2);
        gTrackConsecutiveCurveCounts[i] = get_next_available_memory_addr(D_80163368[i] * 2);
    }

    gCurrentTrackPath = gTrackPaths[0];
    gCurrentTrackLeftPath = gTrackLeftPaths[0];
    gCurrentTrackRightPath = gTrackRightPaths[0];
    gCurrentTrackSectionTypesPath = gTrackSectionTypes[0];
    gCurrentPathPointExpectedRotationPath = gPathExpectedRotation[0];
    gCurrentTrackConsecutiveCurveCountsPath = gTrackConsecutiveCurveCounts[0];

    // zero allocated memory?
    //! @warning does not appear to zero all the above allocated variables.
    for (i = 0; i < 4; i++) {
        clear_path_point(gTrackPaths[i], D_80163368[i]);
        clear_path_point(gTrackLeftPaths[i], D_80163368[i]);
        clear_path_point(gTrackRightPaths[i], D_80163368[i]);
    }

    // Skip several cpu cycles.
    for (i = 0; i < 4; i++) {}

    for (i = 0; i < 4; i++) {
        if (D_80163368[i] >= 2) {
            load_track_path(i);
            calculate_track_boundaries(i);
            analyze_track_section(i);
            analyse_angle_path(i);
            analyze_curved_path(i);
        }
    }

    gSelectedPathCount = *gPathCountByPathIndex;

    CM_ClearVehicles();

    // set_bomb_kart_spawn_positions();
    func_8000EEDC();
}

void init_players(void) {

    UNUSED Camera* camera;
    s32 temp_v0_3;
    s32 i;
    TrackPositionFactorInstruction* var_s5;
    UNUSED s32 temp_v1;
    UNUSED s32 pad;

    for (i = 0; i < NUM_PLAYERS; i++) {
        Player* player = &gPlayerOne[i];

        gPreviousAngleSteering[i] = 0;
        D_80162FF8[i] = 0;
        D_80163010[i] = 0;
        if (!IsPodiumCeremony()) {
            update_player_position_factor(i, 0, 0);
        }
        //! todo: @BUG this doesn't seem right. This variable is metadata.
        cpu_TargetSpeed[i] = CM_GetProps()->CurveTargetSpeed[gCCSelection];
        D_801630E8[i] = 0;
        D_80163100[i] = 0;
        gPreviousPlayerAiOffsetX[i] = 0.0f;
        gPreviousPlayerAiOffsetZ[i] = 0.0f;
        gPreviousCpuTargetSpeed[i] = 0.0f;
        gLapCountByPlayerId[i] = -1;
        gCourseCompletionPercentByPlayerId[i] = 0.0f;
        gTimePlayerLastTouchedFinishLine[i] = 0.0f;
        if (gModeSelection == GRAND_PRIX) {
            if (1) {};
            if (1) {}; // Maybe some debug code?
            gGPCurrentRaceRankByPlayerId[i] = (s32) D_80165270[i];
            gPreviousGPCurrentRaceRankByPlayerId[i] = (s32) D_80165270[i];
        } else {
            gGPCurrentRaceRankByPlayerId[i] = i;
            gPreviousGPCurrentRaceRankByPlayerId[i] = i;
        }
        temp_v0_3 = gGPCurrentRaceRankByPlayerId[i];
        gGPCurrentRacePlayerIdByRank[temp_v0_3] = (s16) i;
        D_80164378[temp_v0_3] = (s16) i;
        gGPCurrentRaceRankByPlayerIdDup[i] = temp_v0_3;
        gWrongDirectionCounter[i] = 0;
        gIsPlayerWrongDirection[i] = 0;
        D_801631E0[i] = 0;
        D_801631F8[i] = 0;
        gNumPathPointsTraversed[i] = -20;
        gPreviousLapProgressScore[i] = -20;
        gGetPlayerByCharacterId[gPlayers[i].characterId] = (s16) i;
        gTrackPositionFactor[i] = 0.0f;
        D_80163090[i] = 0.0f;
        var_s5 = &gPlayerTrackPositionFactorInstruction[i];
        var_s5->unkC = CM_GetProps()->AIMinimumSeparation * (f32) (((i + 1) % 3) - 1);
        var_s5->target = var_s5->unkC;
        var_s5->current = 0.0f;
        var_s5->step = 0.015f;
        reset_cpu_behaviour_none(i);
        gSpeedCPUBehaviour[i] = 0;
        bInMultiPathSection[i] = 0;
        D_80163398[i] = 0;
        D_801633B0[i] = 0;
        D_801633C8[i] = 0;
        D_801633F8[i] = 0;
        gPlayersTrackSectionId[i] = 0;
        gPreviousPlayerZ[i] = player->pos[2];
        gCurrentPlayerLookAhead[i] = 6;
        if (gPlayers[i].type & PLAYER_HUMAN) {
            D_80163330[i] = 3;

        } else {
            D_80163330[i] = 0;
        }

        cpu_enteringPathIntersection[i] = 0;
        cpu_exitingPathIntersection[i] = 0;
        D_80163128[i] = -1;
        D_80163150[i] = -1;
        D_80164538[i] = -1;
        D_801634C0[i] = 0;
        bStopAICrossing[i] = 0;
        gIsPlayerInCurve[i] = true;
    }

#ifdef AVOID_UB
    for (i = 0; i < NUM_CROSSINGS; i++) {
        sCrossingActiveTimer[i] = 0;
    }
#else
    sCrossingActiveTimer[0] = 0;
    sCrossingActiveTimer[1] = 0;
#endif
    if (gDemoMode == DEMO_MODE_INACTIVE) {

        if (gModeSelection == GRAND_PRIX) {
            for (i = 0; i < 2; i++) {
                D_80163344[i] = gGetPlayerByCharacterId[D_80163348[i]];
                D_80163330[D_80163344[i]] = 1;
                D_8016334C[D_80163344[i]] = i;
            }
        }
    }
    if ((gDemoUseController == 1) && (!IsPodiumCeremony())) {
        for (i = 0; i < NUM_PLAYERS; i++) {
            D_80163330[i] = 0;
        }
        if (gModeSelection == VERSUS) {

            D_80163344[0] = cameras->playerId;
            D_80163330[D_80163344[0]] = 1;
            D_8016334C[D_80163344[0]] = 0;

            for (i = 1; i < 2; i++) {
                D_80163344[i] = cameras[i].playerId;
                D_80163330[D_80163344[i]] = 1;
                D_8016334C[D_80163344[i]] = i;
            }
        }
    }

    for (i = 0; i < NUM_PLAYERS; i++) {
        if (1) {};
        gPathIndexByPlayerId[i] = 0;
        gNearestPathPointByPlayerId[i] =
            gPathCountByPathIndex[gPathIndexByPlayerId[i]] - gGPCurrentRaceRankByPlayerId[i] - 4;
    }

    gIsInExtra = 0;
    if (gCCSelection == CC_EXTRA) {
        gIsInExtra = 1;
    }

    for (i = 0; i < 30; i++) {
        D_80162F10[i] = -1;
        D_80162F50[i] = -1;
    }

    D_801631CC = 100000; // 100,000
    D_80164698 = 0.0f;
    D_8016469C = 100.0f;
    D_801646A0 = 0.0f;
    D_80164358 = 0;
    D_8016435A = 1;
    D_8016435C = 1;
    gBestRankedHumanPlayer = 0;
    gIncrementUpdatePlayer = 0;
    D_8016337C = 0;
    gPathStartZ = (f32) gTrackPaths[0][0].posZ; // [i][2]
    D_801634F0 = 0;
    D_801634F4 = 0;
    D_80163488 = 0;
    D_8016348C = 0;
    D_801634EC = 0;
    func_8001AB00();
    if (gDemoUseController == 1) {
        if (gDemoMode == 1) {

            for (i = 0; i < NUM_PLAYERS; i++) {
                if (D_80163330[i] == 1) {
                    gPlayers[i].soundEffects |= 0x02000000;
                }
            }
        }
    }
    copy_courses_cpu_behaviour();
}

// @arg index from 0 to 3.

// Processes course path by index.
// @arg index from 0 to 3.
// Each course can have 1-4 course paths.
void load_track_path(s32 pathIndex) {

    TrackPathPoint* ptr;
    TrackPathPoint* pathDest;
    TrackPathPoint* path;
    s32 var_v0;
    s32 sp24;
    UNUSED s32 pad[2];
    s16 bInvalidPath;
    s32 i;

    if (CM_GetProps()->AIMaximumSeparation >= 1.0f) {
        pathDest = gTrackPaths[pathIndex];
        bInvalidPath = 1;
        if (!IsPodiumCeremony()) {

            TrackPathPoint* pathSrc = CM_GetProps()->PathTable2[pathIndex];
            if (pathSrc == NULL) {
                printf("code_80005FD0.c: Path %d in Course::PathTable2, was NULL.\n  Your track is missing a path\n", pathIndex);
            }

            var_v0 = process_path_data(pathDest, pathSrc);
            gPathCountByPathIndex[pathIndex] = (u16) var_v0;
        } else {
            // Course path included in course_data which has already been loaded into memory.
            // This is how we get the addr to our path data.
            path = CM_GetProps()->PathTable[pathIndex];
            ptr = path;

            for (i = 0; i < 3000; i++, ptr++) {
                if ((u16) ptr->posX == 0x8000) {
                    sp24 = i - 1;
                    bInvalidPath = 0;
                    break;
                }
            }

            // If path data higher than 3000 something has gone wrong.
            // Skip processing the data.
            //! @todo Confirm this comment
            if (!bInvalidPath) {
                var_v0 = func_80011014(pathDest, path, sp24, pathIndex);
                gPathCountByPathIndex[pathIndex] = (u16) var_v0;
            }
        }
    }
}

void calculate_track_boundaries(s32 pathIndex) {
    f32 waypointWidth;
    f32 x1;
    f32 y1;
    f32 z1;
    f32 x2;
    f32 y2;
    f32 z2;
    f32 x_dist;
    f32 z_dist;
    f32 neg_x_dist;
    f32 neg_z_dist;
    f32 xz_dist;
    s32 temp_f16;
    s32 waypointIndex;
    TrackPathPoint* waypoint;
    TrackPathPoint* nextWaypoint;
    TrackPathPoint* var_s1;
    TrackPathPoint* var_s2;

    if (CM_GetProps()->AIMaximumSeparation >= 0.0f) {
        waypointWidth = CM_GetProps()->AIMaximumSeparation;
        waypoint = &gTrackPaths[pathIndex][0];
        var_s1 = &gTrackLeftPaths[pathIndex][0];
        var_s2 = &gTrackRightPaths[pathIndex][0];
        for (waypointIndex = 0; waypointIndex < gPathCountByPathIndex[pathIndex]; waypointIndex++, var_s1++, var_s2++) {
            x1 = waypoint->posX;
            y1 = waypoint->posY;
            z1 = waypoint->posZ;
            waypoint++;
            nextWaypoint = &gTrackPaths[pathIndex][(waypointIndex + 1) % ((s32) gPathCountByPathIndex[pathIndex])];
            x2 = nextWaypoint->posX;
            y2 = nextWaypoint->posY;
            z2 = nextWaypoint->posZ;
            x_dist = x2 - x1;
            z_dist = z2 - z1;
            neg_x_dist = x1 - x2;
            neg_z_dist = z1 - z2;
            xz_dist = sqrtf((x_dist * x_dist) + (z_dist * z_dist));
            temp_f16 = (f32) ((y1 + y2) * 0.5);
            var_s1->posX = ((waypointWidth * z_dist) / xz_dist) + x1;
            var_s1->posY = temp_f16;
            var_s1->posZ = ((waypointWidth * neg_x_dist) / xz_dist) + z1;
            var_s2->posX = ((waypointWidth * neg_z_dist) / xz_dist) + x1;
            var_s2->posY = temp_f16;
            var_s2->posZ = ((waypointWidth * x_dist) / xz_dist) + z1;
        }
    }
}

f32 calculate_track_curvature(s32 pathIndex, u16 waypointIndex) {
    f32 temp_f10_2;
    f32 temp_f8;
    UNUSED f32 pad;
    TrackPathPoint* pathWaypoints;
    f32 x1;
    f32 z1;
    f32 x2;
    f32 z2;
    f32 x3;
    f32 z3;
    f32 temp_f8_2;
    f32 temp_f10;
    s32 waypointCount;
    TrackPathPoint* waypoint3;
    TrackPathPoint* waypoint2;
    TrackPathPoint* waypoint1;
    f32 root2;
    f32 root1;

    if (CM_GetProps()->AIMaximumSeparation <= -1.0f) {
        return 0.0f;
    }
    waypointCount = gPathCountByPathIndex[pathIndex];
    pathWaypoints = gTrackPaths[pathIndex];
    waypoint1 = &pathWaypoints[waypointIndex];
    waypoint2 = &pathWaypoints[(waypointIndex + 1) % waypointCount];
    waypoint3 = &pathWaypoints[(waypointIndex + 2) % waypointCount];
    x1 = waypoint1->posX;
    z1 = waypoint1->posZ;
    x2 = waypoint2->posX;
    z2 = waypoint2->posZ;
    x3 = waypoint3->posX;
    z3 = waypoint3->posZ;
    temp_f8_2 = (((x2 + x3) * 0.5) - x1);
    temp_f10 = (((z2 + z3) * 0.5) - z1);
    waypoint1 = &pathWaypoints[(waypointIndex + 3) % waypointCount];
    waypoint2 = &pathWaypoints[(waypointIndex + 4) % waypointCount];
    waypoint3 = &pathWaypoints[(waypointIndex + 5) % waypointCount];
    x1 = waypoint1->posX;
    z1 = waypoint1->posZ;
    x2 = waypoint2->posX;
    z2 = waypoint2->posZ;
    x3 = waypoint3->posX;
    z3 = waypoint3->posZ;
    temp_f10_2 = (((x2 + x3) * 0.5) - x1);
    temp_f8 = (((z2 + z3) * 0.5) - z1);
    root1 = sqrtf((temp_f10 * temp_f10) + (temp_f8_2 * temp_f8_2));
    root2 = sqrtf((temp_f10_2 * temp_f10_2) + (temp_f8 * temp_f8));
    return -((temp_f10 * temp_f10_2) - (temp_f8_2 * temp_f8)) / (root2 * root1);
}

void analyze_track_section(s32 pathIndex) {
    f64 sectionCurvature;
    UNUSED s32 pad;
    s32 k;
    s32 i;
    s32 j;
    s16* currentSection;
    s32 pathPointCount;
    s16* nextSection;

    if ((s32) CM_GetProps()->AIMaximumSeparation >= 0) {
        pathPointCount = gPathCountByPathIndex[pathIndex];
        currentSection = gTrackSectionTypes[pathIndex];
        for (i = 0; i < pathPointCount; i++, currentSection++) {
            sectionCurvature = calculate_track_curvature(pathIndex, i);
            *currentSection = STRAIGHT;
            if (sectionCurvature > 0.1) {
                *currentSection = RIGHT_CURVE;
            }
            if (sectionCurvature < -0.1) {
                *currentSection = LEFT_CURVE;
            }
        }
        currentSection = gTrackSectionTypes[pathIndex];
        for (i = 0; i < pathPointCount; i++, currentSection++) {
            if (*currentSection == STRAIGHT) {
                // Look ahead for next curved section
                for (j = 1; j < pathPointCount; j++) {
                    nextSection = &gTrackSectionTypes[pathIndex][(i + j) % pathPointCount];
                    switch (*nextSection) {
                        case RIGHT_LEANING_CURVE:
                        case RIGHT_CURVE:
                            for (k = 0; k < j; k++) {
                                gTrackSectionTypes[pathIndex][(i + k) % pathPointCount] = RIGHT_LEANING_CURVE;
                            }
                            i += j;
                            currentSection += j;
                            j = pathPointCount;
                            break;
                        case LEFT_LEANING_CURVE:
                        case LEFT_CURVE:
                            for (k = 0; k < j; k++) {
                                gTrackSectionTypes[pathIndex][(i + k) % pathPointCount] = LEFT_LEANING_CURVE;
                            }
                            i += j;
                            currentSection += j;
                            j = pathPointCount;
                            break;
                    }
                }
            }
        }
    }
}

// Seemingly calculates the atan2 angle between a waypoint and its forward neighbor
s16 calculate_angle_path(s32 pathIndex, s32 waypointIndex) {
    s16 ret;
    Vec3f sp30;
    Vec3f sp24;
    TrackPathPoint* temp_v0;

    temp_v0 = &gTrackPaths[pathIndex][waypointIndex];
    sp30[0] = temp_v0->posX;
    sp30[1] = temp_v0->posY;
    sp30[2] = temp_v0->posZ;
    temp_v0 = &gTrackPaths[pathIndex][(waypointIndex + 1) % gPathCountByPathIndex[pathIndex]];
    sp24[0] = temp_v0->posX;
    sp24[1] = temp_v0->posY;
    sp24[2] = temp_v0->posZ;
    ret = get_angle_between_two_vectors(sp30, sp24);
    return -ret;
}

// Populates gPathExpectedRotation
void analyse_angle_path(s32 pathIndex) {
    s32 waypointIndex;
    u16* angle;

    if (CM_GetProps()->AIMaximumSeparation >= 0.0f) {
        for (angle = (u16*) &gPathExpectedRotation[pathIndex][0], waypointIndex = 0;
             waypointIndex < gPathCountByPathIndex[pathIndex]; waypointIndex++, angle++) {
            *angle = calculate_angle_path(pathIndex, waypointIndex);
        }
    }
}

void analyze_curved_path(s32 pathIndex) {
    s16* trackCurveCount;
    s16 curveCount;
    s16 temp_t0;
    s32 pathPointCount;
    s16* trackSectionType;
    s32 i, j;

    pathPointCount = gPathCountByPathIndex[pathIndex];
    trackSectionType = gTrackSectionTypes[pathIndex];
    trackCurveCount = gTrackConsecutiveCurveCounts[pathIndex];

    for (i = 0; i < pathPointCount; i++) {
        curveCount = 0;
        for (j = 0; j < pathPointCount; j++) {
            temp_t0 = trackSectionType[(i + j) % pathPointCount];
            if ((temp_t0 == LEFT_LEANING_CURVE) || (temp_t0 == RIGHT_LEANING_CURVE)) {
                curveCount += 1;
            } else {
                break;
            }
            if (IsPodiumCeremony()) {
                break;
            }
        }
        *trackCurveCount = curveCount;
        trackCurveCount++;
    }
}

f32 func_80010F40(f32 arg0, f32 arg1, f32 arg2, UNUSED s32 arg3, UNUSED s32 arg4) {
    arg1 = spawn_actor_on_surface(arg0, 2000.0f, arg2);
    check_bounding_collision(&D_80162E70, 1.0f, arg0, arg1, arg2);
    return arg1;
}

f32 func_80010FA0(f32 arg0, f32 arg1, f32 arg2, UNUSED s32 arg3, UNUSED s32 arg4) {
    arg1 = spawn_actor_on_surface(arg0, (f32) ((f64) arg1 + 30.0), arg2);
    check_bounding_collision(&D_80162E70, 10.0f, arg0, arg1, arg2);
    return arg1;
}

/**
 * @return's the number of items processed.
 */
s32 func_80011014(TrackPathPoint* pathDest, TrackPathPoint* path, s32 numPathPoints, UNUSED s32 pathIndex) {
    f32 temp_f24_2;
    f32 temp_f2_3;
    f32 var_f20_2;
    f32 x1;
    f32 z1;
    f32 x2;
    f32 z2;
    f32 x3;
    f32 z3;
    f32 x1_2;
    f32 z1_3;
    f32 temp_f20;
    f32 var_f30;
    f32 temp_f22;
    f32 temp_f16;
    s32 i;

    f32 j;
    s32 var_s0;
    f32 var_f28;

    TrackPathPoint* point1;
    TrackPathPoint* point2;
    TrackPathPoint* point3;
    f32 temp;
    UNUSED TrackPathPoint* dest;
    var_f30 = 0.0f;
    var_s0 = 0;
    temp_f20 = (f32) path[0].posX;
    temp_f22 = (f32) path[0].posZ;
    var_f28 = func_80010F40(temp_f20, 2000.0f, temp_f22, 0, 0);

    for (i = 0; i < numPathPoints; i++) {
        point1 = &path[i % numPathPoints];
        point2 = &path[(i + 1) % numPathPoints];
        point3 = &path[(s32) (i + 2) % numPathPoints];
        x1 = (f32) point1->posX;
        z1 = (f32) point1->posZ;
        x2 = (f32) point2->posX;
        z2 = (f32) point2->posZ;
        x3 = (f32) point3->posX;
        z3 = (f32) point3->posZ;

        temp = 0.05 / (sqrtf(((x2 - x1) * (x2 - x1)) + ((z2 - z1) * (z2 - z1))) +
                       (sqrtf(((x3 - x2) * (x3 - x2)) + ((z3 - z2) * (z3 - z2)))));

        for (j = 0.0f; j <= 1.0; j += temp) {

            temp_f2_3 = (f32) ((1.0 - j) * 0.5 * (1.0 - j));
            z1_3 = (f32) (((1.0 - j) * j) + 0.5);
            temp_f16 = (f32) (j * 0.5 * j);

            temp_f24_2 = (temp_f2_3 * x1) + (z1_3 * x2) + (temp_f16 * x3);
            x1_2 = (temp_f2_3 * z1) + (z1_3 * z2) + (temp_f16 * z3);

            var_f30 +=
                sqrtf(((temp_f24_2 - temp_f20) * (temp_f24_2 - temp_f20)) + ((x1_2 - temp_f22) * (x1_2 - temp_f22)));

            temp_f20 = temp_f24_2;
            temp_f22 = x1_2;

            if ((var_f30 > 20.0f) || ((i == 0) && (j == 0.0))) {
                if (gIsMirrorMode) {
                    // temp_f12 = -temp_f24_2;
                    pathDest->posX = (s16) -temp_f24_2;
                    var_f20_2 = func_80010FA0(-temp_f24_2, var_f28, x1_2, 0, var_s0);
                } else {
                    pathDest->posX = (s16) temp_f24_2;
                    var_f20_2 = func_80010FA0(temp_f24_2, var_f28, x1_2, 0, var_s0);
                }

                pathDest->posZ = (s16) temp_f22;
                pathDest->trackSectionId = get_track_section_id(D_80162E70.meshIndexZX);

                if (var_f20_2 < -500.0) {
                    var_f20_2 = var_f28;
                } else {

                    if (IsRainbowRoad()) {
                        if (var_f20_2 < (var_f28 - 15.0)) {
                            var_f20_2 = (f32) var_f28 - 15.0;
                        }
                    } else if (IsWarioStadium()) {
                        if ((var_s0 >= 1140) && (var_s0 <= 1152)) {
                            var_f20_2 = var_f28;
                        } else {
                            if (var_f20_2 < (var_f28 - 10.0)) {
                                var_f20_2 = (f32) (var_f28 - 4.0);
                            }
                        }
                    } else if (IsDkJungle()) {
                        if ((var_s0 > 204) && (var_s0 < 220)) {
                            var_f20_2 = var_f28;
                        } else {
                            if (var_f20_2 < (var_f28 - 10.0)) {
                                var_f20_2 = (f32) (var_f28 - 4.0);
                            }
                        }
                    } else {
                        if (var_f20_2 < (var_f28 - 10.0)) {
                            var_f20_2 = (f32) var_f28 - 10.0;
                        }
                    }
                }
                var_f28 = var_f20_2;
                pathDest->posY = (s16) (s32) var_f20_2;
                var_f30 = 0.0f;
                pathDest++;
                var_s0 += 1;
            }
        }
    }
    return var_s0;
}

// Returns number of waypoints processed.
s32 process_path_data(TrackPathPoint* dest, TrackPathPoint* src) {
    s16 temp_a0;
    s16 temp_a2;
    s16 temp_a3;
    s32 var_v0;
    s32 var_v1;
    u16 temp_t0;

    var_v1 = 0;
    for (var_v0 = 0; var_v0 < 0x7D0; var_v0++) {
        temp_a0 = src->posX;
        temp_a2 = src->posY;
        temp_a3 = src->posZ;
        temp_t0 = src->trackSectionId;
        src++;
        if (((temp_a0 & 0xFFFF) == 0x8000) && ((temp_a2 & 0xFFFF) == 0x8000) && ((temp_a3 & 0xFFFF) == 0x8000)) {
            break;
        }
        if (gIsMirrorMode != 0) {
            dest->posX = -temp_a0;
        } else {
            dest->posX = temp_a0;
        }
        var_v1++;
        dest->posY = temp_a2;
        dest->posZ = temp_a3;
        dest->trackSectionId = temp_t0;
        dest++;
    }
    return var_v1;
}

s32 generate_2d_path(Path2D* pathDest, TrackPathPoint* pathSrc, s32 numWaypoints) {
    f32 temp_f14_3;
    f32 temp_f16_2;
    UNUSED s32 pad;

    f32 x1;
    f32 z1;
    f32 x2;
    f32 z2;
    f32 x3;
    f32 z3;

    UNUSED s32 pad2;
    f32 temp_f24;

    f32 spA8;
    f32 temp_f26;
    f32 spA0;

    f32 temp_f2_3;

    TrackPathPoint* point1;
    f32 j;
    TrackPathPoint* point2;
    TrackPathPoint* point3;
    s32 i;
    f32 temp_f6 = 0.0f;
    s32 nbElement;
    f32 sp7C;

    spA8 = pathSrc[0].posX;
    spA0 = pathSrc[0].posZ;
    nbElement = 0;

    for (i = 0; i < numWaypoints; i++) {
        point1 = &pathSrc[((i % numWaypoints))];
        point2 = &pathSrc[(((i + 1) % numWaypoints))];
        point3 = &pathSrc[(((i + 2) % numWaypoints))];
        x1 = point1->posX;
        z1 = point1->posZ;
        x2 = point2->posX;
        z2 = point2->posZ;
        x3 = point3->posX;
        z3 = point3->posZ;

        sp7C = 0.05 / (sqrtf(((x2 - x1) * (x2 - x1)) + ((z2 - z1) * (z2 - z1))) +
                       sqrtf(((x3 - x2) * (x3 - x2)) + ((z3 - z2) * (z3 - z2))));

        for (j = 0.0f; j <= 1.0; j += sp7C) {
            temp_f2_3 = (1.0 - j) * 0.5 * (1.0 - j);
            temp_f14_3 = ((1.0 - j) * j) + 0.5;
            temp_f16_2 = j * 0.5 * j;

            temp_f24 = (temp_f2_3 * x1) + (temp_f14_3 * x2) + (temp_f16_2 * x3);
            temp_f26 = (temp_f2_3 * z1) + (temp_f14_3 * z2) + (temp_f16_2 * z3);
            temp_f6 += sqrtf(((temp_f24 - spA8) * (temp_f24 - spA8)) + ((temp_f26 - spA0) * (temp_f26 - spA0)));
            spA8 = temp_f24;
            spA0 = temp_f26;
            if ((temp_f6 > 20.0f) || ((i == 0) && (j == 0.0))) {
                if (gIsMirrorMode) {
                    pathDest->x = (s16) -spA8;
                } else {
                    pathDest->x = (s16) spA8;
                }
                pathDest->z = spA0;
                nbElement += 1;
                pathDest++;
                temp_f6 = 0.0f;
            }
        }
    }
    return nbElement;
}

void copy_courses_cpu_behaviour(void) {
    s32 i;
    for (i = 0; i < NUM_COURSES - 1; i++) {
        gCoursesCPUBehaviour[i] = LOAD_ASSET(CM_GetProps()->AIBehaviour);
    }
}

void reset_cpu_behaviour_none(s32 playerIndex) {
    gCurrentCPUBehaviourId[playerIndex] = 0;
    gPreviousCPUBehaviourId[playerIndex] = 0;
    cpu_BehaviourState[playerIndex] = CPU_BEHAVIOUR_STATE_NONE;
}

void reset_cpu_behaviour(s32 playerIndex) {
    gCurrentCPUBehaviourId[playerIndex] = 0;
    gPreviousCPUBehaviourId[playerIndex] = 0;
    cpu_BehaviourState[playerIndex] = CPU_BEHAVIOUR_STATE_START;
}

void cpu_behaviour_start(s32 playerId, Player* player) {
    u16 playerWaypoint;
    s16 pathPointStart;
    s16 pathPointEnd;
    s32 behaviourType;
    UNUSED s32 test;

    CPUBehaviour* beh = (CPUBehaviour*) LOAD_ASSET(CM_GetProps()->AIBehaviour);

    sCurrentCPUBehaviour = &((CPUBehaviour*) LOAD_ASSET(CM_GetProps()->AIBehaviour))[gCurrentCPUBehaviourId[playerId]];

    playerWaypoint = gNearestPathPointByPlayerId[playerId];

    pathPointStart = sCurrentCPUBehaviour->pathPointStart;
    pathPointEnd = sCurrentCPUBehaviour->pathPointEnd;
    behaviourType = sCurrentCPUBehaviour->type;

    if ((pathPointStart == -1) && (pathPointEnd == -1)) {
        sCurrentCPUBehaviour = &((CPUBehaviour*) LOAD_ASSET(CM_GetProps()->AIBehaviour))[0];
        reset_cpu_behaviour_none(playerId);
        return;
    }
    if ((u32) playerWaypoint == (u32) pathPointStart) {
        cpu_BehaviourState[playerId] = CPU_BEHAVIOUR_STATE_RUNNING;
        gPreviousCPUBehaviourId[playerId] = gCurrentCPUBehaviourId[playerId];
        gCurrentCPUBehaviourId[playerId]++;
        switch (behaviourType) {
            case BEHAVIOUR_1:
                func_80011EC0(playerId, player, player->unk_07C >> 0x10, playerWaypoint);
                break;
            case BEHAVIOUR_HOP:
                kart_hop(player);
                player->effects &= ~0x10;
                D_801630E8[playerId] = 0;
                break;
            case BEHAVIOUR_DRIVE_CENTER:
                gPlayerTrackPositionFactorInstruction[playerId].target = 0.0f;
                break;
            case BEHAVIOUR_DRIVE_LEFT:
                gPlayerTrackPositionFactorInstruction[playerId].target = -0.6f;
                break;
            case BEHAVIOUR_DRIVE_OUTER:
                gPlayerTrackPositionFactorInstruction[playerId].target = 0.6f;
                break;
            case BEHAVIOUR_NORMAL_SPEED:
                gSpeedCPUBehaviour[playerId] = SPEED_CPU_BEHAVIOUR_NORMAL;
                break;
            case BEHAVIOUR_FAST_SPEED:
                gSpeedCPUBehaviour[playerId] = SPEED_CPU_BEHAVIOUR_FAST;
                break;
            case BEHAVIOUR_SLOW_SPEED:
                gSpeedCPUBehaviour[playerId] = SPEED_CPU_BEHAVIOUR_SLOW;
                break;
            case BEHAVIOUR_MAX_SPEED:
                gSpeedCPUBehaviour[playerId] = SPEED_CPU_BEHAVIOUR_MAX;
                break;
            case BEHAVIOUR_9:
                D_801633F8[playerId] = 1;
                D_801631E0[playerId] = 0;
                gPlayers[playerId].effects &= ~0x1000;
                break;
            case BEHAVIOUR_10:
                D_801633F8[playerId] = 0;
                break;
        }
    }
}

void cpu_behaviour_end(s32 playerIndex, Player* player) {
    u16 nearestWaypoint;
    u32 pathPointEnd;
    s32 behaviourType;

    sCurrentCPUBehaviour =
        &((CPUBehaviour*) LOAD_ASSET(CM_GetProps()->AIBehaviour))[gPreviousCPUBehaviourId[playerIndex]];
    nearestWaypoint = gNearestPathPointByPlayerId[playerIndex];
    behaviourType = sCurrentCPUBehaviour->type;
    pathPointEnd = sCurrentCPUBehaviour->pathPointEnd;
    if (nearestWaypoint >= pathPointEnd) {
        switch (behaviourType) {
            case BEHAVIOUR_1:
                player->effects &= ~0x10;
                D_801630E8[playerIndex] = 0;
                cpu_BehaviourState[playerIndex] = CPU_BEHAVIOUR_STATE_START;
                break;
            case BEHAVIOUR_DRIVE_CENTER:
            case BEHAVIOUR_DRIVE_LEFT:
            case BEHAVIOUR_DRIVE_OUTER:
                gPlayerTrackPositionFactorInstruction[playerIndex].target =
                    gPlayerTrackPositionFactorInstruction[playerIndex].unkC;
                cpu_BehaviourState[playerIndex] = CPU_BEHAVIOUR_STATE_START;
                break;
            case BEHAVIOUR_HOP:
            case BEHAVIOUR_NORMAL_SPEED:
            case BEHAVIOUR_FAST_SPEED:
            case BEHAVIOUR_SLOW_SPEED:
            case BEHAVIOUR_9:
            case BEHAVIOUR_10:
            case BEHAVIOUR_MAX_SPEED:
                cpu_BehaviourState[playerIndex] = CPU_BEHAVIOUR_STATE_START;
                break;
            default:
                break;
        }
    }
}

void cpu_behaviour(s32 playerIndex) {
    Player* player = gPlayerOne + playerIndex;

    switch (cpu_BehaviourState[playerIndex]) {
        case CPU_BEHAVIOUR_STATE_NONE:
            break;
        case CPU_BEHAVIOUR_STATE_START:
            cpu_behaviour_start(playerIndex, player);
            break;
        case CPU_BEHAVIOUR_STATE_RUNNING:
            cpu_behaviour_end(playerIndex, player);
            break;
    }
}

void func_80011EC0(s32 arg0, Player* player, s32 arg2, UNUSED u16 arg3) {
    if ((((player->speed / 18.0f) * 216.0f) >= 45.0f) && (D_801630E8[arg0] == 0)) {
        switch (gCurrentTrackSectionTypesPath[sSomeNearestPathPoint]) {
            case RIGHT_LEANING_CURVE:
            case RIGHT_CURVE:
                if ((arg2 >= -9) && (D_80162FF8[arg0] == 0)) {
                    if ((gTrackPositionFactor[arg0] > -0.8) && (gTrackPositionFactor[arg0] < 0.5)) {
                        kart_hop(player);
                        player->effects |= 0x10;
                        D_801630E8[arg0] = 1;
                        break;
                    }
                }
                D_801630E8[arg0] = 2;
                break;
            case LEFT_LEANING_CURVE:
            case LEFT_CURVE:
                if ((arg2 < 0xA) && (D_80162FF8[arg0] == 0)) {
                    if ((gTrackPositionFactor[arg0] > -0.5) && (gTrackPositionFactor[arg0] < 0.8)) {
                        kart_hop(player);
                        player->effects |= 0x10;
                        D_801630E8[arg0] = -1;
                        break;
                    }
                }
                D_801630E8[arg0] = -2;
                break;
        }
    } else {
        D_801630E8[arg0] = 3;
    }
}

#define GET_PATH_LENGTH(waypoint)               \
    for (i = 0;; i++) {                         \
        if ((u16) waypoint[i].posX == 0x8000) { \
            break;                              \
        }                                       \
    }

void generate_train_path(void) {
    s32 i;
    Path2D* temp;
    TrackPathPoint* waypoint = (TrackPathPoint*) LOAD_ASSET(d_course_kalimari_desert_track_unknown_waypoints);
    GET_PATH_LENGTH(waypoint)

    temp = gVehicle2DPathPoint;
    gVehicle2DPathLength = generate_2d_path(temp, waypoint, i - 1);
    D_80162EB0 = spawn_actor_on_surface(temp[0].x, 2000.0f, temp[0].z);
}

void generate_ferry_path(void) {
    TrackPathPoint* waypoint;
    s32 i;

    waypoint = (TrackPathPoint*) LOAD_ASSET(d_course_dks_jungle_parkway_unknown_waypoints2);

    GET_PATH_LENGTH(waypoint)

    gVehicle2DPathLength = generate_2d_path(gVehicle2DPathPoint, waypoint, i - 1);
    D_80162EB2 = -40;
}

void spawn_vehicle_on_road(Vec3f position, Vec3s rotation, Vec3f velocity, s32 waypointIndex,
                           s32 someMultiplierTheSequel, f32 speed) {
    f32 origXPos;
    UNUSED f32 pad;
    f32 origZPos;

    origXPos = position[0];
    origZPos = position[2];
    if (gIsInExtra == 0) {
        func_8000D6D0(position, (s16*) &waypointIndex, speed, someMultiplierTheSequel, 0, 3);
        rotation[0] = 0;
        rotation[1] = -0x8000;
        rotation[2] = 0;
    } else {
        func_8000D940(position, (s16*) &waypointIndex, speed, someMultiplierTheSequel, 0);
        rotation[0] = 0;
        rotation[1] = 0;
        rotation[2] = 0;
    }
    velocity[0] = position[0] - origXPos;
    velocity[2] = position[2] - origZPos;
}

void set_vehicle_pos_path_point(TrainCarStuff* trainCar, Path2D* posXZ, u16 waypoint) {
    trainCar->position[0] = (f32) posXZ->x;
    trainCar->position[1] = (f32) D_80162EB0;
    trainCar->position[2] = (f32) posXZ->z;
    trainCar->actorIndex = -1;
    trainCar->waypointIndex = waypoint;
    trainCar->isActive = 0;
    trainCar->velocity[0] = 0.0f;
    trainCar->velocity[1] = 0.0f;
    trainCar->velocity[2] = 0.0f;
}

/**
 * Set waypoint spawn locations for each rolling stock
 * The railroad has 465 waypoints
 * @arg speed default 5.0f, 120.0f is about the maximum usable value
 */
void init_vehicles_trains(size_t i, size_t numCarriages, f32 speed) {
    u16 waypointOffset;
    TrainCarStuff* ptr1;
    Path2D* pos;
    s32 j;

    gTrainList[i].numCarriages = numCarriages;

    // for (i = 0; i < NUM_TRAINS; i++) {
    //  outputs 160 or 392 depending on the train.
    //  Wraps the value around to always output a valid waypoint.
    waypointOffset = (((i * gVehicle2DPathLength) / 2) + 160) % gVehicle2DPathLength;

    // 120.0f is about the maximum usable value
    gTrainList[i].speed = speed;
    for (j = 0; j < gTrainList[i].numCarriages; j++) {
        waypointOffset += 4;
        ptr1 = &gTrainList[i].passengerCars[j];
        pos = &gVehicle2DPathPoint[waypointOffset];
        set_vehicle_pos_path_point(ptr1, pos, waypointOffset);
    }
    // Smaller offset for the tender
    waypointOffset += 3;
    ptr1 = &gTrainList[i].tender;
    pos = &gVehicle2DPathPoint[waypointOffset];
    set_vehicle_pos_path_point(ptr1, pos, waypointOffset);
    waypointOffset += 4;
    ptr1 = &gTrainList[i].locomotive;
    pos = &gVehicle2DPathPoint[waypointOffset];
    set_vehicle_pos_path_point(ptr1, pos, waypointOffset);

    // Only use locomotive unless overwritten below.
    gTrainList[i].numCars = LOCOMOTIVE_ONLY;
    //}

    // Spawn all rolling stock in single player mode.
    switch (gScreenModeSelection) {
        case SCREEN_MODE_1P: // single player
                             // for (i = 0; i < NUM_TRAINS; i++) {
            gTrainList[i].tender.isActive = 1;

            // clang-format off
                // Same line required for matching...
                for (j = 0; j < gTrainList[i].numCarriages; j++) { gTrainList[i].passengerCars[j].isActive = 1; }
            // clang-format on

            gTrainList[i].numCars = NUM_TENDERS + gTrainList[i].numCarriages;
            //}
            break;

        // Spawn locomotive, tender, and one passenger car in versus 2/3 player mode.
        case SCREEN_MODE_2P_SPLITSCREEN_HORIZONTAL: // multiplayer fall-through
        case SCREEN_MODE_2P_SPLITSCREEN_VERTICAL:
            if (gModeSelection != GRAND_PRIX) {
                // for (i = 0; i < NUM_TRAINS; i++) {
                gTrainList[i].tender.isActive = 1;
                gTrainList[i].passengerCars[4].isActive = 1;
                gTrainList[i].numCars = NUM_TENDERS + NUM_2P_PASSENGER_CARS;
                //}
            }
            break;
    }

    gTrainSmokeTimer = 0;
}
/**
 * @brief sync the train components vehicle with the actor
 *
 * @param trainCar
 * @param orientationY
 */
void sync_train_components(TrainCarStuff* trainCar, s16 orientationY) {
    struct TrainCar* trainCarActor;

    trainCarActor = (struct TrainCar*) GET_ACTOR(trainCar->actorIndex);
    trainCarActor->pos[0] = trainCar->position[0];
    trainCarActor->pos[1] = trainCar->position[1];
    trainCarActor->pos[2] = trainCar->position[2];
    if (gIsMirrorMode != 0) {
        trainCarActor->rot[1] = -orientationY;
    } else {
        trainCarActor->rot[1] = orientationY;
    }
    trainCarActor->velocity[0] = trainCar->velocity[0];
    trainCarActor->velocity[2] = trainCar->velocity[2];
}

void update_vehicle_trains(void) {
    UNUSED s32 pad[3];
    f32 temp_f20;
    TrainCarStuff* car;
    u16 oldWaypointIndex;
    s16 orientationYUpdate;
    f32 temp_f22;
    s32 i;
    s32 j;
    Vec3f smokePos;

    gTrainSmokeTimer += 1;

    for (i = 0; i < NUM_TRAINS; i++) {
        oldWaypointIndex = (u16) gTrainList[i].locomotive.waypointIndex;

        temp_f20 = gTrainList[i].locomotive.position[0];
        temp_f22 = gTrainList[i].locomotive.position[2];

        orientationYUpdate = update_vehicle_following_path(
            gTrainList[i].locomotive.position, (s16*) &gTrainList[i].locomotive.waypointIndex, gTrainList[i].speed);

        gTrainList[i].locomotive.velocity[0] = gTrainList[i].locomotive.position[0] - temp_f20;
        gTrainList[i].locomotive.velocity[2] = gTrainList[i].locomotive.position[2] - temp_f22;

        sync_train_components(&gTrainList[i].locomotive, orientationYUpdate);

        if ((oldWaypointIndex != gTrainList[i].locomotive.waypointIndex) &&
            ((gTrainList[i].locomotive.waypointIndex == 0x00BE) ||
             (gTrainList[i].locomotive.waypointIndex == 0x0140))) { // play crossing bell sound
            func_800C98B8(gTrainList[i].locomotive.position, gTrainList[i].locomotive.velocity,
                          SOUND_ARG_LOAD(0x19, 0x01, 0x80, 0x0E));
        } else if (random_int(100) == 0) { // play train whistle sound
            func_800C98B8(gTrainList[i].locomotive.position, gTrainList[i].locomotive.velocity,
                          SOUND_ARG_LOAD(0x19, 0x01, 0x80, 0x0D));
        }

        gTrainList[i].someFlags = set_vehicle_render_distance_flags(
            gTrainList[i].locomotive.position, TRAIN_SMOKE_RENDER_DISTANCE, gTrainList[i].someFlags);
        // Renders locomotive smoke on all screens if any player is within range.
        if ((((s16) gTrainSmokeTimer % 5) == 0) && (gTrainList[i].someFlags != 0)) {
            smokePos[0] = gTrainList[i].locomotive.position[0];
            smokePos[1] = (f32) ((f64) gTrainList[i].locomotive.position[1] + 65.0);
            smokePos[2] = (f32) ((f64) gTrainList[i].locomotive.position[2] + 25.0);
            adjust_position_by_angle(smokePos, gTrainList[i].locomotive.position, orientationYUpdate);
            spawn_train_smoke(i, smokePos, 1.1f);
        }

        car = &gTrainList[i].tender;

        if (car->isActive == 1) {
            temp_f20 = car->position[0];
            temp_f22 = car->position[2];
            orientationYUpdate =
                update_vehicle_following_path(car->position, (s16*) &car->waypointIndex, gTrainList[i].speed);
            car->velocity[0] = car->position[0] - temp_f20;
            car->velocity[2] = car->position[2] - temp_f22;
            sync_train_components(car, orientationYUpdate);
        }

        for (j = 0; j < gTrainList[i].numCarriages; j++) {
            car = &gTrainList[i].passengerCars[j];
            if (car->isActive == 1) {
                temp_f20 = car->position[0];
                temp_f22 = car->position[2];

                orientationYUpdate =
                    update_vehicle_following_path(car->position, (s16*) &car->waypointIndex, gTrainList[i].speed);
                car->velocity[0] = car->position[0] - temp_f20;
                car->velocity[2] = car->position[2] - temp_f22;
                sync_train_components(car, orientationYUpdate);
            }
        }
    }
}

void handle_trains_interactions(s32 playerId, Player* player) {
    TrainCarStuff* trainCar;
    f32 playerPosX;
    f32 playerPosZ;
    f32 x_dist;
    f32 z_dist;
    s32 trainIndex;
    s32 passengerCarIndex;

    if (D_801631E0[playerId] != 1) {
        if (!(player->effects & 0x01000000)) {
            playerPosX = player->pos[0];
            playerPosZ = player->pos[2];
            for (trainIndex = 0; trainIndex < NUM_TRAINS; trainIndex++) {
                trainCar = &gTrainList[trainIndex].locomotive;
                x_dist = playerPosX - trainCar->position[0];
                z_dist = playerPosZ - trainCar->position[2];
                if ((x_dist > -100.0) && (x_dist < 100.0)) {
                    if ((z_dist > -100.0) && (z_dist < 100.0)) {
                        if (is_collide_with_vehicle(trainCar->position[0], trainCar->position[2], trainCar->velocity[0],
                                                    trainCar->velocity[2], 60.0f, 20.0f, playerPosX, playerPosZ) == 1) {
                            player->soundEffects |= REVERSE_SOUND_EFFECT;
                        }
                        trainCar = &gTrainList[trainIndex].tender;
                        if (trainCar->isActive == 1) {
                            if (is_collide_with_vehicle(trainCar->position[0], trainCar->position[2],
                                                        trainCar->velocity[0], trainCar->velocity[2], 30.0f, 20.0f,
                                                        playerPosX, playerPosZ) == 1) {
                                player->soundEffects |= REVERSE_SOUND_EFFECT;
                            }
                        }
                    }
                }

                for (passengerCarIndex = 0; passengerCarIndex < gTrainList[trainIndex].numCarriages;
                     passengerCarIndex++) {
                    trainCar = &gTrainList[trainIndex].passengerCars[passengerCarIndex];
                    x_dist = playerPosX - trainCar->position[0];
                    z_dist = playerPosZ - trainCar->position[2];
                    if (trainCar->isActive == 1) {
                        if ((x_dist > -100.0) && (x_dist < 100.0)) {
                            if ((z_dist > -100.0) && (z_dist < 100.0)) {
                                if (is_collide_with_vehicle(trainCar->position[0], trainCar->position[2],
                                                            trainCar->velocity[0], trainCar->velocity[2], 30.0f, 20.0f,
                                                            playerPosX, playerPosZ) == 1) {
                                    player->soundEffects |= REVERSE_SOUND_EFFECT;
                                }
                            }
                        }
                    }
                }
            }
        }
    }
}

/**
 * Appears to check if the train is close to the crossing.
 * Implements sCrossingActiveTimer as a counter
 */
void func_80013054(void) {
    f32 temp_f16;
    f32 temp_f18;
    f32 temp_f12;
    s32 i;
    isCrossingTriggeredByIndex[0] = 0;
    isCrossingTriggeredByIndex[1] = 0;

    for (i = 0; i < NUM_TRAINS; i++) {
        temp_f16 = gTrainList[i].locomotive.waypointIndex / ((f32) gVehicle2DPathLength);
        temp_f18 = 0.72017354f;
        temp_f12 = 0.42299348f;

        if (((temp_f12 - 0.1) < temp_f16) &&
            (temp_f16 < ((((f64) gTrainList[i].numCars) * 0.01) + (temp_f12 + 0.01)))) {

            isCrossingTriggeredByIndex[0] = 1;
        }
        if (((temp_f18 - 0.1) < temp_f16) &&
            (temp_f16 < ((((f64) gTrainList[i].numCars) * 0.01) + (temp_f18 + 0.01)))) {

            isCrossingTriggeredByIndex[1] = 1;
        }
    }

    for (i = 0; i < NUM_CROSSINGS; i++) {
        if (isCrossingTriggeredByIndex[i] == 1) {
            sCrossingActiveTimer[i] += 1;
        } else {
            sCrossingActiveTimer[i] = 0;
        }
    }
}

void check_ai_crossing_distance(s32 playerId) {
    bStopAICrossing[playerId] = 0;
    if (IsKalimariDesert()) {
        if ((!(D_801631E0[playerId] != 0)) ||
            (set_vehicle_render_distance_flags(gPlayers[playerId].pos, TRAIN_CROSSING_AI_DISTANCE, 0))) {

            if ((isCrossingTriggeredByIndex[1] == 1) && ((sCrossingActiveTimer[1]) > FRAMES_SINCE_CROSSING_ACTIVATED)) {

                if ((sSomeNearestPathPoint > 176) && (sSomeNearestPathPoint < 182)) {
                    bStopAICrossing[playerId] = 1;
                }
            }
            if ((isCrossingTriggeredByIndex[0] == 1) && ((sCrossingActiveTimer[0]) > FRAMES_SINCE_CROSSING_ACTIVATED)) {
                if ((sSomeNearestPathPoint >= 306) && (sSomeNearestPathPoint < 310)) {
                    bStopAICrossing[playerId] = 1;
                }
            }
        }
    }
}

void init_vehicles_ferry(void) {
    PaddleBoatStuff* paddleBoat;
    s32 i;
    Path2D* temp_a2;
    u16 temp;
    for (i = 0; i < NUM_ACTIVE_PADDLE_BOATS; i++) {
        temp = i * 0xB4;
        paddleBoat = &gPaddleBoats[i];
        temp_a2 = &gVehicle2DPathPoint[temp];
        paddleBoat->position[0] = temp_a2->x;
        paddleBoat->position[1] = D_80162EB2;
        paddleBoat->position[2] = temp_a2->z;
        paddleBoat->waypointIndex = i * 0xB4;
        paddleBoat->actorIndex = -1;

        if (gPlayerCount >= 3) {
            paddleBoat->isActive = 0;
        } else {
            paddleBoat->isActive = 1;
        }
        paddleBoat->velocity[0] = 0.0f;
        paddleBoat->velocity[1] = 0.0f;
        paddleBoat->velocity[2] = 0.0f;
        paddleBoat->speed = 1.6666666f;
        paddleBoat->rotY = 0;
    }
    gFerrySmokeTimer = 0;
}

void update_vehicle_paddle_boats(void) {
    PaddleBoatStuff* paddleBoat;
    Path2D* waypoint;
    s32 i;
    struct Actor* paddleBoatActor;
    f32 temp_f26;
    f32 temp_f28;
    f32 temp_f30;
    s16 temp_a1;
    s32 temp;
    s16 var_v1;
    Vec3f sp94;
    Vec3f sp88;
    UNUSED s32 pad;
    Vec3f smokePos;
    UNUSED s32 pad2;
    gFerrySmokeTimer += 1;
    for (i = 0; i < NUM_ACTIVE_PADDLE_BOATS; i++) {
        paddleBoat = &gPaddleBoats[i];
        if (paddleBoat->isActive == 1) {
            temp_f26 = paddleBoat->position[0];
            temp_f28 = paddleBoat->position[1];
            temp_f30 = paddleBoat->position[2];
            update_vehicle_following_path(paddleBoat->position, (s16*) &paddleBoat->waypointIndex, paddleBoat->speed);
            paddleBoat->someFlags = set_vehicle_render_distance_flags(paddleBoat->position, BOAT_SMOKE_RENDER_DISTANCE,
                                                                      paddleBoat->someFlags);
            if ((((s16) gFerrySmokeTimer % 10) == 0) && (paddleBoat->someFlags != 0)) {
                smokePos[0] = (f32) ((f64) paddleBoat->position[0] - 30.0);
                smokePos[1] = (f32) ((f64) paddleBoat->position[1] + 180.0);
                smokePos[2] = (f32) ((f64) paddleBoat->position[2] + 45.0);
                adjust_position_by_angle(smokePos, paddleBoat->position, paddleBoat->rotY);
                spawn_ferry_smoke(i, smokePos, 1.1f);
                smokePos[0] = (f32) ((f64) paddleBoat->position[0] + 30.0);
                smokePos[1] = (f32) ((f64) paddleBoat->position[1] + 180.0);
                smokePos[2] = (f32) ((f64) paddleBoat->position[2] + 45.0);
                adjust_position_by_angle(smokePos, paddleBoat->position, paddleBoat->rotY);
                spawn_ferry_smoke(i, smokePos, 1.1f);
            }
            if (random_int(100) == 0) {
                if (random_int(2) == 0) {
                    func_800C98B8(paddleBoat->position, paddleBoat->velocity, SOUND_ARG_LOAD(0x19, 0x01, 0x80, 0x47));
                } else {
                    func_800C98B8(paddleBoat->position, paddleBoat->velocity, SOUND_ARG_LOAD(0x19, 0x01, 0x80, 0x48));
                }
            }
            sp94[0] = temp_f26;
            sp94[1] = temp_f28;
            sp94[2] = temp_f30;
            waypoint = &gVehicle2DPathPoint[(paddleBoat->waypointIndex + 5) % gVehicle2DPathLength];
            sp88[0] = (f32) waypoint->x;
            sp88[1] = (f32) D_80162EB0;
            sp88[2] = (f32) waypoint->z;
            temp_a1 = get_angle_between_path(sp94, sp88);
            temp = temp_a1 - paddleBoat->rotY;
            var_v1 = temp;
            if (var_v1 < 0) {
                var_v1 = -var_v1;
            }
            if (var_v1 >= 0x1771) {
                if (paddleBoat->speed > 0.2) {
                    paddleBoat->speed -= 0.04;
                }
                if (var_v1 >= 0x3D) {
                    var_v1 = 0x003C;
                }
            } else {
                if (paddleBoat->speed < 2.0) {
                    paddleBoat->speed += 0.02;
                }
                if (var_v1 >= 0x1F) {
                    var_v1 = 0x001E;
                }
            }
            if (temp >= 0x8000) {
                paddleBoat->rotY -= var_v1;
            } else if (temp > 0) {
                paddleBoat->rotY += var_v1;
            } else if (temp < -0x7FFF) {
                paddleBoat->rotY += var_v1;
            } else if (temp < 0) {
                paddleBoat->rotY -= var_v1;
            }
            paddleBoat->velocity[0] = paddleBoat->position[0] - temp_f26;
            paddleBoat->velocity[1] = paddleBoat->position[1] - temp_f28;
            paddleBoat->velocity[2] = paddleBoat->position[2] - temp_f30;
            paddleBoatActor = GET_ACTOR(paddleBoat->actorIndex);
            paddleBoatActor->pos[0] = paddleBoat->position[0];
            paddleBoatActor->pos[1] = paddleBoat->position[1];
            paddleBoatActor->pos[2] = paddleBoat->position[2];
            if (gIsMirrorMode != 0) {
                paddleBoatActor->rot[1] = -paddleBoat->rotY;
            } else {
                paddleBoatActor->rot[1] = paddleBoat->rotY;
            }
            paddleBoatActor->velocity[0] = paddleBoat->velocity[0];
            paddleBoatActor->velocity[1] = paddleBoat->velocity[1];
            paddleBoatActor->velocity[2] = paddleBoat->velocity[2];
        }
    }
}

void handle_paddle_boats_interactions(Player* player) {
    s32 someIndex;
    PaddleBoatStuff* tempPaddleWheelBoat;
    f32 x_diff;
    f32 y_diff;
    f32 z_diff;
    f32 playerX;
    f32 playerZ;
    f32 playerY;

    if (!((player->effects & 0x01000000)) && (!(player->effects & HIT_BY_ITEM_EFFECT))) {
        playerX = player->pos[0];
        playerY = player->pos[1];
        playerZ = player->pos[2];
        for (someIndex = 0; someIndex < NUM_ACTIVE_PADDLE_BOATS; someIndex++) {
            tempPaddleWheelBoat = &gPaddleBoats[someIndex];
            if (tempPaddleWheelBoat->isActive == 1) {
                x_diff = playerX - tempPaddleWheelBoat->position[0];
                y_diff = playerY - tempPaddleWheelBoat->position[1];
                z_diff = playerZ - tempPaddleWheelBoat->position[2];
                if ((x_diff > -300.0) && (x_diff < 300.0)) {
                    if ((z_diff > -300.0) && (z_diff < 300.0)) {
                        if ((is_collide_with_vehicle(tempPaddleWheelBoat->position[0], tempPaddleWheelBoat->position[2],
                                                     tempPaddleWheelBoat->velocity[0], tempPaddleWheelBoat->velocity[2],
                                                     200.0f, 60.0f, playerX, playerZ) == 1) &&
                            (y_diff < 60.0)) {
                            player->soundEffects |= 0x80000;
                        }
                    }
                }
            }
        }
    }
}

void initialize_toads_turnpike_vehicle(f32 speedA, f32 speedB, s32 numVehicles, s32 arg3, VehicleStuff* vehicleList,
                                       TrackPathPoint* waypointList) {
    VehicleStuff* veh;
    TrackPathPoint* temp_v0;
    s32 i;
    u16 waypointOffset;
    s32 numWaypoints = gPathCountByPathIndex[0];
    for (i = 0; i < numVehicles; i++) {
        waypointOffset = (((i * numWaypoints) / numVehicles) + arg3) % numWaypoints;
        veh = &vehicleList[i];
        temp_v0 = &waypointList[waypointOffset];
        veh->position[0] = (f32) temp_v0->posX;
        veh->position[1] = (f32) temp_v0->posY;
        veh->position[2] = (f32) temp_v0->posZ;
        veh->actorIndex = -1;
        veh->waypointIndex = waypointOffset;
        veh->unused = 0;
        veh->velocity[0] = 0.0f;
        veh->velocity[1] = 0.0f;
        veh->velocity[2] = 0.0f;
        veh->someFlags = 0;
        veh->someFlagsTheSequel = 0;
        if (gModeSelection == TIME_TRIALS) {
            veh->someType = (i % 3);
        } else {
            veh->someType = random_int(3);
        }
        veh->someMultiplierTheSequel = (f32) ((f64) (f32) (veh->someType - 1) * 0.6);
        if (((gCCSelection > CC_50) || (gModeSelection == TIME_TRIALS)) && (veh->someType == 2)) {
            veh->speed = speedA;
        } else {
            veh->speed = speedB;
        }
        veh->rotation[0] = 0;
        veh->rotation[2] = 0;
        if (gIsInExtra == 0) {
            veh->rotation[1] = func_8000D6D0(veh->position, (s16*) &veh->waypointIndex, veh->speed,
                                             veh->someMultiplierTheSequel, 0, 3);
        } else {
            veh->rotation[1] =
                func_8000D940(veh->position, (s16*) &veh->waypointIndex, veh->speed, veh->someMultiplierTheSequel, 0);
        }
    }
    sVehicleSoundRenderCounter = 10;
}

f32 func_80013C74(s16 someType, s16 waypointIndex) {
    f32 var_f2;

    var_f2 = 0.0f;
    if (waypointIndex < 0x28A) {
        switch (someType) {
            case 0:
                var_f2 = -0.7f;
                break;
            case 1:
                break;
            case 2:
                var_f2 = 0.7f;
                break;
            default:
                break;
        }
    } else {
        switch (someType) {
            case 0:
            case 1:
                var_f2 = -0.5f;
                break;
            case 2:
                var_f2 = 0.5f;
                break;
            default:
                break;
        }
    }
    return var_f2;
}

void update_vehicle_follow_path_point(VehicleStuff* vehicle) {
    f32 temp_f0_2;
    f32 temp_f0_3;
    f32 sp5C;
    f32 sp58;
    f32 sp54;
    f32 temp_f2_2;
    s16 var_a1;
    s16 thing;
    Vec3f sp40;
    Vec3f sp34;
    struct Actor* vehicleActor;

    sp5C = vehicle->position[0];
    sp58 = vehicle->position[1];
    sp54 = vehicle->position[2];
    sp40[0] = sp58;
    sp40[1] = 0.0f;
    sp40[2] = 0.0f;
    temp_f0_2 = func_80013C74(vehicle->someType, vehicle->waypointIndex);
    if (vehicle->someMultiplierTheSequel < temp_f0_2) {
        vehicle->someMultiplierTheSequel = vehicle->someMultiplierTheSequel + 0.06;
        if (temp_f0_2 < vehicle->someMultiplierTheSequel) {
            vehicle->someMultiplierTheSequel = temp_f0_2;
        }
    }
    if (temp_f0_2 < vehicle->someMultiplierTheSequel) {
        vehicle->someMultiplierTheSequel = vehicle->someMultiplierTheSequel - 0.06;
        if (vehicle->someMultiplierTheSequel < temp_f0_2) {
            vehicle->someMultiplierTheSequel = temp_f0_2;
        }
    }
    if (gIsInExtra == 0) {
        var_a1 = func_8000D6D0(vehicle->position, (s16*) &vehicle->waypointIndex, vehicle->speed,
                               vehicle->someMultiplierTheSequel, 0, 3);
    } else {
        var_a1 = func_8000D940(vehicle->position, (s16*) &vehicle->waypointIndex, vehicle->speed,
                               vehicle->someMultiplierTheSequel, 0);
    }
    adjust_angle(&vehicle->rotation[1], var_a1, 100);
    temp_f0_3 = vehicle->position[0] - sp5C;
    temp_f2_2 = vehicle->position[2] - sp54;
    sp34[0] = vehicle->position[1];
    sp34[1] = 0.0f;
    sp34[2] = sqrtf((temp_f0_3 * temp_f0_3) + (temp_f2_2 * temp_f2_2));
    thing = get_angle_between_two_vectors(sp40, sp34);
    adjust_angle(&vehicle->rotation[0], -thing, 100);
    vehicle->velocity[0] = vehicle->position[0] - sp5C;
    vehicle->velocity[1] = vehicle->position[1] - sp58;
    vehicle->velocity[2] = vehicle->position[2] - sp54;
    vehicleActor = GET_ACTOR(vehicle->actorIndex);
    vehicleActor->pos[0] = vehicle->position[0];
    vehicleActor->pos[1] = vehicle->position[1];
    vehicleActor->pos[2] = vehicle->position[2];
    vehicleActor->rot[0] = vehicle->rotation[0];
    if (gIsMirrorMode != 0) {
        vehicleActor->rot[1] = -vehicle->rotation[1];
    } else {
        vehicleActor->rot[1] = vehicle->rotation[1];
    }
    vehicleActor->rot[2] = vehicle->rotation[2];
    vehicleActor->velocity[0] = vehicle->velocity[0];
    vehicleActor->velocity[1] = vehicle->velocity[1];
    vehicleActor->velocity[2] = vehicle->velocity[2];
}

void handle_vehicle_interactions(s32 playerId, Player* player, VehicleStuff* vehicle, f32 arg3, f32 arg4, s32 arg5,
                                 u32 soundBits) {
    f32 temp_f12;
    f32 temp_f14;
    f32 temp_f22;

    s32 i;

    f32 spC4;
    f32 spC0;
    f32 spBC;

    if (((D_801631E0[playerId] != 1) || ((((player->type & PLAYER_HUMAN) != 0)) && !(player->type & PLAYER_CPU))) &&
        !(player->effects & 0x01000000)) {

        spC4 = player->pos[0];
        spC0 = player->pos[1];
        spBC = player->pos[2];

        for (i = 0; i < arg5; i++) {
            temp_f12 = spC4 - vehicle->position[0];
            temp_f22 = spC0 - vehicle->position[1];
            temp_f14 = spBC - vehicle->position[2];

            if (((temp_f12) > -100.0) && ((temp_f12) < 100.0)) {
                if ((temp_f22 > -20.0) && (temp_f22 < 20.0)) {

                    if (((temp_f14) > -100.0) && ((temp_f14) < 100.0)) {
                        if (is_collide_with_vehicle(vehicle->position[0], vehicle->position[2], vehicle->velocity[0],
                                                    vehicle->velocity[2], arg3, arg4, spC4, spBC) == (s32) 1) {
                            player->soundEffects |= REVERSE_SOUND_EFFECT;
                        }
                    }
                }
            }
            if ((player->type & PLAYER_HUMAN) && !(player->type & PLAYER_CPU)) {
                if (((temp_f12) > -300.0) && ((temp_f12) < 300.0) && ((temp_f22 > -20.0)) && (temp_f22 < 20.0) &&
                    (((temp_f14) > -300.0)) && ((temp_f14) < 300.0)) {
                    if ((sVehicleSoundRenderCounter > 0) && (vehicle->someFlags == 0)) {
                        sVehicleSoundRenderCounter -= 1;
                        vehicle->someFlags |= (RENDER_VEHICLE << playerId);
                        func_800C9D80(vehicle->position, vehicle->velocity, soundBits);
                    }
                } else {
                    if (vehicle->someFlags != 0) {
                        vehicle->someFlags &= ~(RENDER_VEHICLE << playerId);
                        if (vehicle->someFlags == 0) {
                            sVehicleSoundRenderCounter += 1;
                            func_800C9EF4(vehicle->position, soundBits);
                        }
                    }
                }

                if (((temp_f12) > -200.0) && ((temp_f12) < 200.0) && ((temp_f22 > -20.0)) && (temp_f22 < 20.0) &&
                    (((temp_f14) > -200.0)) && ((temp_f14) < 200.0)) {
                    if (!(vehicle->someFlagsTheSequel & ((1 << playerId)))) {

                        s32 var_s1 = 0;
                        u16 path = gPathCountByPathIndex[0];
                        s32 t1;
                        s32 t2;

                        switch (gIsInExtra) {
                            case 0:
                                t1 = is_path_point_in_range(vehicle->waypointIndex,
                                                            gNearestPathPointByPlayerId[playerId], 10, 0, path);
                                if ((gIsPlayerWrongDirection[playerId] == 0) && (t1 > 0) &&
                                    (player->speed < vehicle->speed)) {
                                    var_s1 = 1;
                                }
                                if ((gIsPlayerWrongDirection[playerId] == 1) && (t1 > 0)) {
                                    var_s1 = 1;
                                }
                                break;
                            case 1:
                                t2 = is_path_point_in_range(vehicle->waypointIndex,
                                                            gNearestPathPointByPlayerId[playerId], 0, 10, path);
                                if (t2 > 0) {
                                    if (random_int(2) == 0) {
                                        // temp_v1_2 = gIsPlayerWrongDirection[playerId];
                                        if (gIsPlayerWrongDirection[playerId] == 0) {
                                            var_s1 = 1;
                                        }
                                        if ((gIsPlayerWrongDirection[playerId] == 1) &&
                                            (player->speed < vehicle->speed)) {
                                            var_s1 = 1;
                                        }
                                    } else {
                                        vehicle->someFlagsTheSequel |= ((1 << playerId));
                                    }
                                }
                                break;
                        }
                        if (var_s1 == 1) {

                            u32 soundBits2 = SOUND_ARG_LOAD(0x19, 0x01, 0x70, 0x3B);

                            switch (soundBits) {
                                case SOUND_ARG_LOAD(0x51, 0x01, 0x80, 0x05):
                                    soundBits2 = SOUND_ARG_LOAD(0x19, 0x01, 0x70, 0x3B);
                                    if (random_int(4) == 0) {
                                        soundBits2 = SOUND_ARG_LOAD(0x19, 0x01, 0x70, 0x3C);
                                    }
                                    break;
                                case SOUND_ARG_LOAD(0x51, 0x01, 0x80, 0x02):
                                    if (random_int(2) != 0) {
                                        soundBits2 = SOUND_ARG_LOAD(0x19, 0x01, 0x70, 0x3D);
                                    } else {
                                        soundBits2 = SOUND_ARG_LOAD(0x19, 0x01, 0x70, 0x3E);
                                    }
                                    break;
                                case SOUND_ARG_LOAD(0x51, 0x01, 0x80, 0x03):
                                    if (random_int(2) != 0) {
                                        soundBits2 = SOUND_ARG_LOAD(0x19, 0x01, 0x70, 0x3F);
                                    } else {
                                        soundBits2 = SOUND_ARG_LOAD(0x19, 0x01, 0x70, 0x40);
                                    }
                                    break;
                                case SOUND_ARG_LOAD(0x51, 0x01, 0x80, 0x04):
                                    if (random_int(2) != 0) {
                                        soundBits2 = SOUND_ARG_LOAD(0x19, 0x01, 0x70, 0x41);
                                    } else {
                                        soundBits2 = SOUND_ARG_LOAD(0x19, 0x01, 0x70, 0x42);
                                    }
                                    break;
                            }
                            vehicle->someFlagsTheSequel |= ((1 << playerId));
                            func_800C98B8(vehicle->position, vehicle->velocity, soundBits2);
                        }
                    }
                } else {
                    if (vehicle->someFlagsTheSequel & ((1 << playerId))) {
                        vehicle->someFlagsTheSequel &= ~((1 << playerId));
                    }
                }
            }
            vehicle++;
        }
    }
}

f32 player_track_position_factor_vehicle(s16 arg0, f32 arg1, s16 arg2) {
    if (arg2 < 0x28A) {
        switch (arg0) {
            case 0:
                if (arg1 < 0.0) {
                    arg1 = 0.0f;
                }
                break;
            case 1:
                if (arg1 < 0.0) {
                    arg1 = -0.8f;
                } else {
                    arg1 = 0.8f;
                }
                break;
            case 2:
                if (arg1 >= 0.0) {
                    arg1 = 0.0f;
                }
                break;
            default:
                break;
        }
    } else {
        switch (arg0) {
            case 0:
            case 1:
                arg1 = 0.5f;
                break;
            case 2:
                arg1 = -0.5f;
                break;
            default:
                break;
        }
    }
    return arg1;
}

void update_player_track_position_factor_from_vehicle(s32 playerId, s32 arg1, VehicleStuff* vehicle) {
    UNUSED s32 var_v1;
    s32 var_v0;
    s32 var_s2;
    s32 waypointCount;
    u16 temp_a1;
    UNUSED VehicleStuff* tempVehicle;

    waypointCount = gPathCountByPathIndex[0];
    if (!(gPlayers[playerId].speed < 1.6666666666666667)) {
        for (var_s2 = 0; var_s2 < arg1; var_s2++, vehicle++) {
            temp_a1 = vehicle->waypointIndex;
            for (var_v0 = 0; var_v0 < 0x18; var_v0 += 3) {
                if (((sSomeNearestPathPoint + var_v0) % waypointCount) == temp_a1) {
                    gPlayerTrackPositionFactorInstruction[playerId].target = player_track_position_factor_vehicle(
                        vehicle->someType, gTrackPositionFactor[playerId], temp_a1);
                    return;
                }
            }
        }
    }
}

void init_vehicles_box_trucks(void) {
    f32 a = ((gCCSelection * 90.0) / 216.0f) + 4.583333333333333;
    f32 b = ((gCCSelection * 90.0) / 216.0f) + 2.9166666666666665;
    s32 numTrucks = NUM_RACE_BOX_TRUCKS;
    if (gModeSelection == TIME_TRIALS) {
        numTrucks = NUM_TIME_TRIAL_BOX_TRUCKS;
    }
    initialize_toads_turnpike_vehicle(a, b, numTrucks, 0, gBoxTruckList, &gTrackPaths[0][0]);
}

void update_vehicle_box_trucks(void) {
    s32 loopIndex;
    for (loopIndex = 0; loopIndex < NUM_RACE_BOX_TRUCKS; loopIndex++) {
        update_vehicle_follow_path_point(&gBoxTruckList[loopIndex]);
    }
}

void handle_box_trucks_interactions(s32 playerId, Player* player) {
    handle_vehicle_interactions(playerId, player, gBoxTruckList, 55.0f, 12.5f, NUM_RACE_BOX_TRUCKS,
                                SOUND_ARG_LOAD(0x51, 0x01, 0x80, 0x03));
}

void update_player_track_position_factor_from_box_trucks(s32 playerId) {
    update_player_track_position_factor_from_vehicle(playerId, NUM_RACE_BOX_TRUCKS, gBoxTruckList);
}

void init_vehicles_school_buses(void) {
    s32 numBusses;
    f32 a = ((gCCSelection * 90.0) / 216.0f) + 4.583333333333333;
    f32 b = ((gCCSelection * 90.0) / 216.0f) + 2.9166666666666665;

    numBusses = NUM_RACE_SCHOOL_BUSES;
    if (gModeSelection == TIME_TRIALS) {
        numBusses = NUM_TIME_TRIAL_SCHOOL_BUSES;
    }
    initialize_toads_turnpike_vehicle(a, b, numBusses, 75, gSchoolBusList, &gTrackPaths[0][0]);
}

void update_vehicle_school_bus(void) {
    s32 loopIndex;
    for (loopIndex = 0; loopIndex < NUM_RACE_SCHOOL_BUSES; loopIndex++) {
        update_vehicle_follow_path_point(&gSchoolBusList[loopIndex]);
    }
}

void handle_school_buses_interactions(s32 playerId, Player* player) {
    handle_vehicle_interactions(playerId, player, gSchoolBusList, 70.0f, 12.5f, NUM_RACE_SCHOOL_BUSES,
                                SOUND_ARG_LOAD(0x51, 0x01, 0x80, 0x02));
}

void update_player_track_position_factor_from_buses(s32 playerId) {
    update_player_track_position_factor_from_vehicle(playerId, NUM_RACE_SCHOOL_BUSES, gSchoolBusList);
}

void init_vehicles_trucks(void) {
    s32 numTrucks;
    f32 a = ((gCCSelection * 90.0) / 216.0f) + 4.583333333333333;
    f32 b = ((gCCSelection * 90.0) / 216.0f) + 2.9166666666666665;

    numTrucks = NUM_RACE_TANKER_TRUCKS;
    if (gModeSelection == TIME_TRIALS) {
        numTrucks = NUM_TIME_TRIAL_TANKER_TRUCKS;
    }
    initialize_toads_turnpike_vehicle(a, b, numTrucks, 50, gTankerTruckList, &gTrackPaths[0][0]);
}

void update_vehicle_tanker_trucks(void) {
    s32 loopIndex;
    for (loopIndex = 0; loopIndex < NUM_RACE_TANKER_TRUCKS; loopIndex++) {
        update_vehicle_follow_path_point(&gTankerTruckList[loopIndex]);
    }
}

void handle_tanker_trucks_interactions(s32 playerId, Player* player) {
    handle_vehicle_interactions(playerId, player, gTankerTruckList, 55.0f, 12.5f, NUM_RACE_TANKER_TRUCKS,
                                SOUND_ARG_LOAD(0x51, 0x01, 0x80, 0x04));
}

void update_player_track_position_factor_from_tanker_truck(s32 playerId) {
    update_player_track_position_factor_from_vehicle(playerId, NUM_RACE_TANKER_TRUCKS, gTankerTruckList);
}

void init_vehicles_cars(void) {
    s32 numCars;
    f32 a = ((gCCSelection * 90.0) / 216.0f) + 4.583333333333333;
    f32 b = ((gCCSelection * 90.0) / 216.0f) + 2.9166666666666665;

    numCars = NUM_RACE_CARS;
    if (gModeSelection == TIME_TRIALS) {
        numCars = NUM_TIME_TRIAL_CARS;
    }
    initialize_toads_turnpike_vehicle(a, b, numCars, 25, gCarList, &gTrackPaths[0][0]);
}

void update_vehicle_cars(void) {
    s32 loopIndex;
    for (loopIndex = 0; loopIndex < NUM_RACE_CARS; loopIndex++) {
        update_vehicle_follow_path_point(&gCarList[loopIndex]);
    }
}

void handle_cars_interactions(s32 playerId, Player* player) {
    handle_vehicle_interactions(playerId, player, gCarList, 11.5f, 8.5f, NUM_RACE_CARS,
                                SOUND_ARG_LOAD(0x51, 0x01, 0x80, 0x05));
}

void update_player_track_position_factor_from_cars(s32 playerId) {
    update_player_track_position_factor_from_vehicle(playerId, NUM_RACE_CARS, gCarList);
}

void func_80014D30(s32 cameraId, s32 pathIndex) {
    s16 cameraWaypoint;
    TrackPathPoint* temp_v0;

    cameraWaypoint = gNearestPathPointByCameraId[cameraId];
    temp_v0 = &gTrackPaths[pathIndex][cameraWaypoint];
    check_bounding_collision(&cameras[cameraId].collision, 10.0f, (f32) temp_v0->posX, (f32) temp_v0->posY + 30.0f,
                             (f32) temp_v0->posZ);
}

void func_80014DE4(s32 cameraIndex) {
    s32 cameraId;

    D_801646CC = 0;
    D_80164678[cameraIndex] = D_80164670[cameraIndex];
    if ((gModeSelection != 1) && ((GetCupCursorPosition() == COURSE_ONE) || (gDemoMode == (u16) 1))) {
        D_80164678[cameraIndex] = 0;
    } else if ((D_80164678[cameraIndex] != 0) && (D_80164678[cameraIndex] != (s16) 1) &&
               (D_80164678[cameraIndex] != 2) && (D_80164678[cameraIndex] != 3)) {
        D_80164678[cameraIndex] = 0;
    }
    D_80164680[cameraIndex] = -1;
    D_80163238 = 0;
    D_801646C0[cameraIndex] = 0;
    D_801646C8 = 0;
    D_801646D0[cameraIndex].unk0 = 0;
    D_801646D0[cameraIndex].unk2 = 0;
    D_801646D0[cameraIndex].unk4 = 0;
    if ((gModeSelection == 1) && (gCourseMapInit == 0)) {
        D_80164678[cameraIndex] = 0;
    }

    for (cameraId = 0; cameraId < 4; cameraId++) {
        gNearestPathPointByCameraId[cameraId] = 0;
    }
}

f32 func_80014EE4(f32 arg0, s32 arg1) {
    f32 temp_f0;
    f64 temp_f2;

    temp_f0 = D_80164498[arg1];
    switch (D_80164678[arg1]) { /* irregular */
        default:
            arg0 = 40.0f;
            break;
        case 0:
            temp_f2 = 40.0;
            temp_f2 += temp_f0;
            if (temp_f2 < arg0) {
                arg0 -= 1.0;
                if (arg0 < temp_f2) {
                    arg0 = temp_f2;
                }
            }
            if (arg0 < temp_f2) {
                arg0 += 1.0;
                if (temp_f2 < arg0) {
                    arg0 = temp_f2;
                    ;
                }
            }
            break;
        case 1:
            temp_f2 = 60.0;
            temp_f2 += temp_f0;
            if (arg0 < temp_f2) {
                arg0 += 1.0;
                if (temp_f2 < arg0) {
                    arg0 = temp_f2;
                }
            }
            if (temp_f2 < arg0) {
                arg0 -= 1.0;
                if (arg0 < temp_f2) {
                    arg0 = temp_f2;
                    ;
                }
            }
            break;
        case 3:
            temp_f2 = 60.0;
            temp_f2 += temp_f0;
            if (arg0 < temp_f2) {
                arg0 += 0.5;
                if (temp_f2 < arg0) {
                    arg0 = temp_f2;
                }
            }
            if (temp_f2 < arg0) {
                arg0 -= 0.5;
                if (arg0 < temp_f2) {
                    arg0 = temp_f2;
                }
            }
            break;
        case 2:
            temp_f2 = 60.0;
            temp_f2 += temp_f0;
            if (arg0 < temp_f2) {
                arg0 += 1.0;
                if (temp_f2 < arg0) {
                    arg0 = temp_f2;
                }
            }
            if (temp_f2 < arg0) {
                arg0 -= 1.0;
                if (arg0 < temp_f2) {
                    arg0 = temp_f2;
                }
            }
            break;
    }
    return arg0;
}

void calculate_camera_up_vector(Camera* camera, s32 cameraIndex) {
    f32 xnorm;
    f32 ynorm;
    f32 znorm;
    f32 xdiff;
    f32 ydiff;
    f32 zdiff;
    f32 distance;
    f32 sp28;
    u16 thing;

    thing = D_801646C0[cameraIndex];
    if (thing == 0) {
        camera->up[0] = 0.0f;
        camera->up[2] = 0.0f;
        camera->up[1] = 1.0f;
    } else {
        xdiff = camera->lookAt[0] - camera->pos[0];
        ydiff = camera->lookAt[1] - camera->pos[1];
        zdiff = camera->lookAt[2] - camera->pos[2];
        distance = sqrtf((xdiff * xdiff) + (ydiff * ydiff) + (zdiff * zdiff));
        xnorm = xdiff / distance;
        ynorm = ydiff / distance;
        znorm = zdiff / distance;
        sp28 = 1.0 - coss(thing);
        camera->up[0] = (sp28 * xnorm * ynorm) - (sins(thing) * znorm);
        camera->up[1] = coss(thing) + (sp28 * ynorm * ynorm);
        camera->up[2] = (sins(thing) * xnorm) + (sp28 * ynorm * znorm);
    }
}

UNUSED void func_8001530C(void) {
}

void func_80015314(s32 playerId, UNUSED f32 arg1, s32 cameraId) {
    Camera* temp_a0;
    Player* temp_a1;

    // wtf is up with the pointer accesses here?
    // What aren't they just doing thing = &some_pointer[some_index]?
    temp_a1 = gPlayerOne;
    temp_a0 = camera1;
    temp_a1 += playerId;
    temp_a0 += cameraId;
    temp_a0->unk_2C = temp_a1->rotation[1];
    func_80015390(temp_a0, temp_a1, 0);
}

void func_80015390(Camera* camera, UNUSED Player* player, UNUSED s32 arg2) {
    UNUSED s32 pad[6];
    f32 temp_f12;
    f32 sp90;
    f32 temp_f14;
    Player* temp_s1;
    f32 sp84;
    f32 sp80;
    f32 sp7C;
    UNUSED Vec3f pad2;
    Vec3f sp64;
    UNUSED s32 pad3[9];
    s16 var_a2;

    /*
    Doing the more sensible:
    temp_s1 = &gPlayerOne[camera->playerId];
    leads to some regalloc differences
    */
    temp_s1 = gPlayerOne;
    temp_s1 += camera->playerId;
    if (temp_s1->unk_078 == 0) {
        var_a2 = 0x0064;
    } else if (temp_s1->unk_078 < 0) {
        var_a2 = 0xA0 - (temp_s1->unk_078 / 16);
    } else {
        var_a2 = 0xA0 + (temp_s1->unk_078 / 16);
    }
    if (!((temp_s1->effects & 0x80) || (temp_s1->effects & 0x40))) {
        adjust_angle(&camera->unk_2C, temp_s1->rotation[1], var_a2);
    }
    func_8001D794(temp_s1, camera, sp64, &sp84, &sp80, &sp7C, camera->unk_2C);
    check_bounding_collision(&camera->collision, 10.0f, sp84, sp80, sp7C);
    camera->lookAt[0] = sp64[0];
    camera->lookAt[1] = sp64[1];
    camera->lookAt[2] = sp64[2];
    camera->pos[0] = sp84;
    camera->pos[1] = sp80;
    camera->pos[2] = sp7C;
    temp_f12 = camera->lookAt[0] - camera->pos[0];
    sp90 = camera->lookAt[1] - camera->pos[1];
    temp_f14 = camera->lookAt[2] - camera->pos[2];
    camera->rot[1] = atan2s(temp_f12, temp_f14);
    camera->rot[0] = atan2s(sqrtf((temp_f12 * temp_f12) + (temp_f14 * temp_f14)), sp90);
    camera->rot[2] = 0;
}

void func_80015544(s32 playerId, f32 arg1, s32 cameraId, s32 pathIndex) {
    Camera* camera;

    f32 temp_f12;
    f32 temp_f2;
    s32 test = gPathCountByPathIndex[pathIndex];

    D_80164688[cameraId] = arg1;
    camera = cameras + cameraId;
    gNearestPathPointByCameraId[cameraId] = (gNearestPathPointByPlayerId[playerId] + 10) % test;

    set_track_offset_position(gNearestPathPointByCameraId[cameraId], arg1, pathIndex);

    D_801645F8[cameraId] = gOffsetPosition[0];
    D_80164638[cameraId] = gOffsetPosition[2];

    temp_f2 = (f32) gTrackPaths[pathIndex][gNearestPathPointByCameraId[cameraId]].posY;

    temp_f12 = spawn_actor_on_surface(gOffsetPosition[0], (f32) (temp_f2 + 30.0), gOffsetPosition[2]);
    if ((temp_f12 < (temp_f2 - 20.0)) || (temp_f12 >= 3000.0)) {
        D_80164618[cameraId] = (f32) (temp_f2 + 10.0);
    } else {
        D_80164618[cameraId] = (f32) (temp_f12 + 10.0);
    }
    D_80164648[cameraId] = 0.0f;
    camera->pos[0] = D_801645F8[cameraId];
    camera->pos[1] = D_80164618[cameraId];
    camera->pos[2] = D_80164638[cameraId];
}

void func_8001577C(Camera* camera, UNUSED Player* playerArg, UNUSED s32 arg2, s32 cameraId) {
    s16 cameraWaypoint;
    s16 playerWaypoint;
    UNUSED s32 pad;
    f32 xdiff;
    f32 ydiff;
    f32 zdiff;
    UNUSED s32 pad2;
    s32 playerId;
    UNUSED s32 pad3[9];
    Player* player;
    s32 waypointDiff;
    s32 pathIndex;

    playerId = camera->playerId;
    pathIndex = gPathIndexByPlayerId[playerId];
    player = gPlayerOne;
    player += playerId;
    gNearestPathPointByCameraId[cameraId] =
        func_8000D33C(camera->pos[0], camera->pos[1], camera->pos[2], gNearestPathPointByCameraId[cameraId], pathIndex);
    playerWaypoint = gNearestPathPointByPlayerId[playerId];
    cameraWaypoint = gNearestPathPointByCameraId[cameraId];
    if (is_path_point_in_range(playerWaypoint, cameraWaypoint, 0x0032U, 0x000FU, gPathCountByPathIndex[pathIndex]) <=
        0) {
        func_8001A348(cameraId, D_80164688[cameraId], D_80164680[cameraId]);
    } else {
        if (gTrackPositionFactor[playerId] < (-0.7)) {
            waypointDiff = playerWaypoint - cameraWaypoint;
            if ((D_80164688[cameraId] < (-0.5)) && ((waypointDiff * waypointDiff) < 5)) {
                func_8001A348(cameraId, 1.0f, 3);
                goto alable;
            }
        }
        // clang-format off
        // I hate this, but a fakematch is a fakematch
        if (gTrackPositionFactor[playerId] > 0.7) { waypointDiff = playerWaypoint - cameraWaypoint; if ((D_80164688[cameraId] > 0.5) && ((waypointDiff * waypointDiff) < 5)) {
                func_8001A348(cameraId, -1.0f, 2);
                // clang-format on
            }
        }
    }
alable:
    camera->pos[0] = D_801645F8[cameraId];
    camera->pos[1] = D_80164618[cameraId];
    camera->pos[2] = D_80164638[cameraId];
    camera->lookAt[0] = player->pos[0];
    camera->lookAt[1] = player->pos[1] + 6.0;
    camera->lookAt[2] = player->pos[2];
    func_80014D30(cameraId, pathIndex);
    xdiff = camera->lookAt[0] - camera->pos[0];
    ydiff = camera->lookAt[1] - camera->pos[1];
    zdiff = camera->lookAt[2] - camera->pos[2];
    camera->rot[1] = atan2s(xdiff, zdiff);
    camera->rot[0] = atan2s(sqrtf((xdiff * xdiff) + (zdiff * zdiff)), ydiff);
    camera->rot[2] = 0;
}

void func_80015A9C(s32 playerId, f32 arg1, s32 cameraId, s16 pathIndex) {
    Camera* camera = cameras + cameraId;

    D_80164688[cameraId] = arg1;
    gNearestPathPointByCameraId[cameraId] = gNearestPathPointByPlayerId[playerId] + 10;
    gNearestPathPointByCameraId[cameraId] = (gNearestPathPointByCameraId[cameraId]) % gPathCountByPathIndex[pathIndex];

    set_track_offset_position(gNearestPathPointByCameraId[cameraId], arg1, pathIndex);

    D_801645F8[cameraId] = gOffsetPosition[0];
    D_80164618[cameraId] = (f32) gTrackPaths[pathIndex][gNearestPathPointByCameraId[cameraId]].posY;
    D_80164638[cameraId] = gOffsetPosition[2];

    D_80164648[cameraId] = gPlayers[playerId].speed / 5.0f;
    if ((f64) D_80164648[cameraId] < 0.0) {
        D_80164648[cameraId] = 0.0f;
    }

    camera->pos[0] = D_801645F8[cameraId];
    camera->pos[1] = D_80164618[cameraId];
    camera->pos[2] = D_80164638[cameraId];
}

void func_80015C94(Camera* camera, UNUSED Player* unusedPlayer, UNUSED s32 arg2, s32 cameraId) {
    s16 playerWaypoint;
    s16 cameraWaypoint;
    UNUSED s32 stackPadding0;
    f32 xdiff2;
    f32 ydiff2;
    f32 zdiff2;
    Player* player;
    s32 playerId;
    f32 midX;
    f32 midY;
    f32 midZ;
    f32 xdiff;
    f32 ydiff;
    f32 zdiff;
    f32 distance;
    UNUSED s32 stackPadding1;
    s16 waypoint1;
    s16 waypoint2;
    UNUSED f32 var_f18;
    UNUSED f32 var_f20;
    f32 temp_f2_2;
    s32 pathIndex;

    playerId = camera->playerId;
    player = gPlayerOne;
    player += playerId;
    D_80163238 = playerId;
    pathIndex = gPathIndexByPlayerId[playerId];
    gNearestPathPointByCameraId[cameraId] =
        func_8000D33C(camera->pos[0], camera->pos[1], camera->pos[2], gNearestPathPointByCameraId[cameraId], pathIndex);
    playerWaypoint = gNearestPathPointByPlayerId[playerId];
    cameraWaypoint = gNearestPathPointByCameraId[cameraId];
    if (is_path_point_in_range(playerWaypoint, cameraWaypoint, 0x0032U, 0x000FU, gPathCountByPathIndex[pathIndex]) <=
        0) {
        func_8001A348(cameraId, D_80164688[cameraId], D_80164680[cameraId]);
    } else if ((gTrackPositionFactor[playerId] < -0.5) && (D_80164688[cameraId] < -0.5)) {
        func_8001A348(cameraId, 1.0f, 7);
    } else if ((gTrackPositionFactor[playerId] > 0.5) && (D_80164688[cameraId] > 0.5)) {
        func_8001A348(cameraId, -1.0f, 6);
    }
    waypoint1 = (gNearestPathPointByCameraId[cameraId] + 1) % gPathCountByPathIndex[pathIndex];
    waypoint2 = (gNearestPathPointByCameraId[cameraId] + 2) % gPathCountByPathIndex[pathIndex];
    set_track_offset_position(waypoint1, D_80164688[cameraId], pathIndex);
    midX = gOffsetPosition[0] * 0.5;
    midZ = gOffsetPosition[2] * 0.5;
    set_track_offset_position(waypoint2, D_80164688[cameraId], pathIndex);
    midX += gOffsetPosition[0] * 0.5;
    midZ += gOffsetPosition[2] * 0.5;
    midY = (gTrackPaths[pathIndex][waypoint1].posY + gTrackPaths[pathIndex][waypoint2].posY) / 2.0;
    xdiff = midX - D_801645F8[cameraId];
    ydiff = midY - D_80164618[cameraId];
    zdiff = midZ - D_80164638[cameraId];
    distance = sqrtf((xdiff * xdiff) + (ydiff * ydiff) + (zdiff * zdiff));
    if (distance != 0.0) {
        midX = D_801645F8[cameraId] + ((D_80164648[cameraId] * xdiff) / distance);
        midY = D_80164618[cameraId] + ((D_80164648[cameraId] * ydiff) / distance);
        midZ = D_80164638[cameraId] + ((D_80164648[cameraId] * zdiff) / distance);
    } else {
        midX = D_801645F8[cameraId];
        midY = D_80164618[cameraId];
        midZ = D_80164638[cameraId];
    }
    camera->pos[0] = midX;
    camera->pos[2] = midZ;
    temp_f2_2 = spawn_actor_on_surface(midX, (midY + 30.0), midZ);
    if ((temp_f2_2 < (midY - 20.0)) || (temp_f2_2 >= 3000.0)) {
        camera->pos[1] = midY + 10.0;
    } else {
        camera->pos[1] = temp_f2_2 + 8.0;
    }
    D_801645F8[cameraId] = midX;
    D_80164618[cameraId] = midY;
    D_80164638[cameraId] = midZ;
    camera->lookAt[0] = player->pos[0];
    camera->lookAt[1] = player->pos[1] + 6.0;
    camera->lookAt[2] = player->pos[2];
    func_80014D30(cameraId, pathIndex);
    xdiff2 = camera->lookAt[0] - camera->pos[0];
    ydiff2 = camera->lookAt[1] - camera->pos[1];
    zdiff2 = camera->lookAt[2] - camera->pos[2];
    camera->rot[1] = atan2s(xdiff2, zdiff2);
    camera->rot[0] = atan2s(sqrtf((xdiff2 * xdiff2) + (zdiff2 * zdiff2)), ydiff2);
    camera->rot[2] = 0;
}

void func_800162CC(s32 playerId, f32 arg1, s32 cameraId, s16 pathIndex) {
    Camera* camera = cameras + cameraId;

    D_80164688[cameraId] = arg1;
    gNearestPathPointByCameraId[cameraId] = gNearestPathPointByPlayerId[playerId];
    gNearestPathPointByCameraId[cameraId] = (gNearestPathPointByCameraId[cameraId]) % gPathCountByPathIndex[pathIndex];

    set_track_offset_position(gNearestPathPointByCameraId[cameraId], arg1, pathIndex);

    D_801645F8[cameraId] = gOffsetPosition[0];
    D_80164618[cameraId] = (f32) gTrackPaths[pathIndex][gNearestPathPointByCameraId[cameraId]].posY;
    D_80164638[cameraId] = gOffsetPosition[2];

    D_80164658[cameraId] = gPlayers[playerId].speed;
    D_80164648[cameraId] = gPlayers[playerId].speed;

    camera->pos[0] = D_801645F8[cameraId];
    camera->pos[1] = D_80164618[cameraId];
    camera->pos[2] = D_80164638[cameraId];
}

void func_80016494(Camera* camera, UNUSED Player* unusedPlayer, UNUSED s32 arg2, s32 cameraId) {
    s16 cameraWaypoint;
    s16 playerWaypoint;
    UNUSED s32 stackPadding0;
    f32 xdiff2;
    f32 ydiff2;
    f32 zdiff2;
    Player* player;
    s32 playerId;
    f32 midX;
    f32 midY;
    f32 midZ;
    f32 xdiff;
    f32 ydiff;
    f32 zdiff;
    f32 distance;
    UNUSED f32 sp98;
    s16 waypoint1;
    s16 waypoint2;
    UNUSED f32 sp94;
    UNUSED f32 sp90;
    UNUSED s32 stackPadding1;
    s32 pathIndex;
    f32 temp_f2_5;

    playerId = camera->playerId;
    player = gPlayerOne;
    D_80164648[cameraId] += ((D_80164658[cameraId] - D_80164648[cameraId]) * 0.5f);
    D_80163238 = playerId;
    pathIndex = gPathIndexByPlayerId[playerId];
    player += playerId;
    gNearestPathPointByCameraId[cameraId] =
        func_8000D33C(camera->pos[0], camera->pos[1], camera->pos[2], gNearestPathPointByCameraId[cameraId], pathIndex);
    temp_f2_5 = (gTrackPositionFactor[playerId] - D_80164688[cameraId]);
    temp_f2_5 *= temp_f2_5;
    playerWaypoint = gNearestPathPointByPlayerId[playerId];
    cameraWaypoint = gNearestPathPointByCameraId[cameraId];
    if (is_path_point_in_range(playerWaypoint, cameraWaypoint, 0x000FU, 0x000FU, gPathCountByPathIndex[pathIndex]) <=
        0) {
        func_8001A348(cameraId, D_80164688[cameraId], D_80164680[cameraId]);
    } else {
        if ((gTrackPositionFactor[playerId] < 0.0) && (D_80164688[cameraId] < 0.0) && (temp_f2_5 < 0.01)) {
            func_8001A348(cameraId, 1.0f, 5);
        } else {
            if ((gTrackPositionFactor[playerId] > 0.0) && (D_80164688[cameraId] > 0.0) && (temp_f2_5 < 0.01)) {
                func_8001A348(cameraId, -1.0f, 4);
            } else {
                if ((cameraWaypoint < playerWaypoint) && ((playerWaypoint - cameraWaypoint) < 0xA)) {
                    D_80164658[cameraId] = gPlayers[playerId].speed + 0.4;
                }
                if ((playerWaypoint < cameraWaypoint) && ((cameraWaypoint - playerWaypoint) < 0xA)) {
                    D_80164658[cameraId] = gPlayers[playerId].speed - 0.4;
                }
                if (D_80164658[cameraId] > 10.0) {
                    D_80164658[cameraId] = 10.0f;
                }
                if (D_80164658[cameraId] < 0.0) {
                    D_80164658[cameraId] = 0.0f;
                }
            }
        }
    }
    waypoint1 = (gNearestPathPointByCameraId[cameraId] + 1) % gPathCountByPathIndex[pathIndex];
    waypoint2 = (gNearestPathPointByCameraId[cameraId] + 2) % gPathCountByPathIndex[pathIndex];
    set_track_offset_position(waypoint1, D_80164688[cameraId], pathIndex);
    midX = gOffsetPosition[0] * 0.5;
    midZ = gOffsetPosition[2] * 0.5;
    set_track_offset_position(waypoint2, D_80164688[cameraId], pathIndex);
    midX += gOffsetPosition[0] * 0.5;
    midZ += gOffsetPosition[2] * 0.5;
    midY = (gTrackPaths[pathIndex][waypoint1].posY + gTrackPaths[pathIndex][waypoint2].posY) / 2.0;
    xdiff = midX - D_801645F8[cameraId];
    ydiff = midY - D_80164618[cameraId];
    zdiff = midZ - D_80164638[cameraId];
    distance = sqrtf((xdiff * xdiff) + (ydiff * ydiff) + (zdiff * zdiff));
    if (distance != 0.0) {
        midX = D_801645F8[cameraId] + ((D_80164648[cameraId] * xdiff) / distance);
        midY = D_80164618[cameraId] + ((D_80164648[cameraId] * ydiff) / distance);
        midZ = D_80164638[cameraId] + ((D_80164648[cameraId] * zdiff) / distance);
    } else {
        midX = D_801645F8[cameraId];
        midY = D_80164618[cameraId];
        midZ = D_80164638[cameraId];
    }
    camera->pos[0] = midX;
    camera->pos[2] = midZ;
    temp_f2_5 = spawn_actor_on_surface(midX, midY + 30.0, midZ);
    if ((temp_f2_5 < (midY - 20.0)) || (temp_f2_5 >= 3000.0)) {
        camera->pos[1] = midY + 10.0;
    } else {
        camera->pos[1] = temp_f2_5 + 10.0;
    }
    D_801645F8[cameraId] = midX;
    D_80164618[cameraId] = midY;
    D_80164638[cameraId] = midZ;
    camera->lookAt[0] = player->pos[0];
    camera->lookAt[1] = player->pos[1] + 6.0;
    camera->lookAt[2] = player->pos[2];
    func_80014D30(cameraId, pathIndex);
    xdiff2 = camera->lookAt[0] - camera->pos[0];
    ydiff2 = camera->lookAt[1] - camera->pos[1];
    zdiff2 = camera->lookAt[2] - camera->pos[2];
    camera->rot[1] = atan2s(xdiff2, zdiff2);
    camera->rot[0] = atan2s(sqrtf((xdiff2 * xdiff2) + (zdiff2 * zdiff2)), ydiff2);
    camera->rot[2] = 0;
}

void func_80016C3C(UNUSED s32 playerId, UNUSED f32 arg1, s32 cameraId) {
    UNUSED s32 stackPadding0;
    UNUSED s32 stackPadding1;
    f32 temp_f2;
    UNUSED s32 stackPadding2;
    f32 temp_f12;
    TrackPathPoint** path;
    f32 sp54;
    s32 temp_s0;
    s16 sp48;
    s16 sp44;
    Camera* camera;

    if (random_int(0x0064U) < 0x32) {
        D_80164688[cameraId] = 0.1f;
    } else {
        D_80164688[cameraId] = -0.1f;
    }
    D_80163DD8[cameraId] = 0;
    if (IsYoshiValley()) {
        D_80163DD8[cameraId] = random_int(4U);
        D_80164688[cameraId] = 0.0f;
    }
    temp_s0 = gPathCountByPathIndex[D_80163DD8[cameraId]];
    gNearestPathPointByCameraId[cameraId] %= temp_s0;
    set_track_offset_position(gNearestPathPointByCameraId[cameraId], D_80164688[cameraId], 0);
    D_801645F8[cameraId] = gOffsetPosition[0];
    D_80164618[cameraId] = gTrackPaths[0][gNearestPathPointByCameraId[cameraId]].posY;
    path = gTrackPaths;
    D_80164638[cameraId] = gOffsetPosition[2];
    D_80164658[cameraId] = 16.666666f;
    D_80164648[cameraId] = 0.0f;
    sp48 = (gNearestPathPointByCameraId[cameraId] + 0xA) % temp_s0;
    sp44 = (gNearestPathPointByCameraId[cameraId] + 0xB) % temp_s0;
    set_track_offset_position(sp48, D_80164688[cameraId], 0);
    temp_f2 = gOffsetPosition[0] * 0.5;
    temp_f12 = gOffsetPosition[2] * 0.5;
    set_track_offset_position(sp44, D_80164688[cameraId], 0);
    temp_f2 += gOffsetPosition[0] * 0.5;
    temp_f12 += gOffsetPosition[2] * 0.5;
    sp48 = (gNearestPathPointByCameraId[cameraId] + 0x5) % temp_s0;
    sp44 = (gNearestPathPointByCameraId[cameraId] + 0x6) % temp_s0;
    sp54 = (path[0][sp48].posY + path[0][sp44].posY) * 0.5f;
    camera = cameras;
    camera += cameraId;
    camera->lookAt[0] = temp_f2;
    camera->lookAt[2] = temp_f12;
    camera->lookAt[1] = sp54 + 8.0;
    camera->pos[0] = D_801645F8[cameraId];
    camera->pos[1] = D_80164618[cameraId];
    camera->pos[2] = D_80164638[cameraId];
}

void func_80017054(Camera* camera, UNUSED Player* player, UNUSED s32 index, s32 cameraId);
void func_80017054(Camera* camera, UNUSED Player* player, UNUSED s32 index, s32 cameraId) {
    s32 stackPadding0;
    s32 stackPadding1;
    f32 spAC;
    f32 spA8;
    f32 spA4;
    s32 stackPadding2;
    s32 stackPadding3;
    f32 camX;
    f32 camY;
    f32 camZ;
    f32 lookAtX;
    f32 lookAtY;
    f32 lookAtZ;
    f32 diffX;
    f32 diffY;
    f32 diffZ;
    s32 stackPadding7;
    s32 stackPadding8;
    s16 waypoint1;
    s16 waypoint2;
    f32 stackPadding9;
    s32 playerId;
    f32 distance;
    s32 pathIndex;
    s32 sp58;
    s16 sp56;

    playerId = camera->playerId;
    D_80164648[cameraId] += (D_80164658[cameraId] - D_80164648[cameraId]) * 0.5f;
    pathIndex = D_80163DD8[cameraId];
    sp58 = gPathCountByPathIndex[pathIndex];
    D_80163238 = playerId;
    sp56 = gNearestPathPointByCameraId[cameraId];
    gNearestPathPointByCameraId[cameraId] = func_8000D33C(camera->pos[0], camera->pos[1], camera->pos[2], gNearestPathPointByCameraId[cameraId], pathIndex);
    if (IsYoshiValley()) {
        if ((sp56 != gNearestPathPointByCameraId[cameraId]) && (gNearestPathPointByCameraId[cameraId] == 1)) {
            D_80163DD8[cameraId] = random_int(4);
            pathIndex = D_80163DD8[cameraId];
            gNearestPathPointByCameraId[cameraId] = func_8000D33C(camera->pos[0], camera->pos[1], camera->pos[2], gNearestPathPointByCameraId[cameraId], pathIndex);
        }
    }
    waypoint1 = (gNearestPathPointByCameraId[cameraId] + 0xA) % sp58;
    waypoint2 = (gNearestPathPointByCameraId[cameraId] + 0xB) % sp58;
    set_track_offset_position(waypoint1, D_80164688[cameraId], pathIndex);
    lookAtX = gOffsetPosition[0] * 0.5;
    lookAtZ = gOffsetPosition[2] * 0.5;
    set_track_offset_position(waypoint2, D_80164688[cameraId], pathIndex);
    lookAtX += gOffsetPosition[0] * 0.5;
    lookAtZ += gOffsetPosition[2] * 0.5;

    waypoint1 = (gNearestPathPointByCameraId[cameraId] + 5) % sp58;
    waypoint2 = (gNearestPathPointByCameraId[cameraId] + 6) % sp58;
    lookAtY = (gTrackPaths[pathIndex][waypoint1].posY + gTrackPaths[pathIndex][waypoint2].posY) * 0.5f;
    waypoint1 = (gNearestPathPointByCameraId[cameraId] + 1) % sp58;
    waypoint2 = (gNearestPathPointByCameraId[cameraId] + 2) % sp58;
    set_track_offset_position(waypoint1, D_80164688[cameraId], pathIndex);
    camX = gOffsetPosition[0] * 0.5;
    camZ = gOffsetPosition[2] * 0.5;
    set_track_offset_position(waypoint2, D_80164688[cameraId], pathIndex);
    camX += gOffsetPosition[0] * 0.5;
    camZ += gOffsetPosition[2] * 0.5;
    camY = (gTrackPaths[pathIndex][waypoint1].posY + gTrackPaths[pathIndex][waypoint2].posY) * 0.5f;

    diffX = camX - D_801645F8[cameraId];
    diffY = camY - D_80164618[cameraId];
    diffZ = camZ - D_80164638[cameraId];

    distance = sqrtf(((diffX * diffX) + (diffY * diffY)) + (diffZ * diffZ));
    if (distance != 0.0) {
        camX = D_801645F8[cameraId] + (((stackPadding9 = D_80164648[cameraId]) * diffX) / distance);
        camY = D_80164618[cameraId] + ((D_80164648[cameraId] * diffY) / distance);
        camZ = D_80164638[cameraId] + ((D_80164648[cameraId] * diffZ) / distance);
    } else {
        camX = D_801645F8[cameraId];
        camY = D_80164618[cameraId];
        camZ = D_80164638[cameraId];
    }

    if (camX < -10000.0 || camX > 10000.0) {
        if (lookAtX < -10000.0 || lookAtX > 10000.0) {}
    }
    camera->pos[0] = camX;
    camera->pos[2] = camZ;
    camera->pos[1] = camY + 10.0;

    D_801645F8[cameraId] = camX;
    D_80164618[cameraId] = camY;
    D_80164638[cameraId] = camZ;

    if (lookAtX < -10000.0 || lookAtX > 10000.0) {}
    if (lookAtZ < -10000.0 || lookAtZ > 10000.0) {}
    camera->lookAt[0] = lookAtX;
    camera->lookAt[1] = lookAtY + 8.0;
    camera->lookAt[2] = lookAtZ;
    func_80014D30(cameraId, pathIndex);
    spAC = camera->lookAt[0] - camera->pos[0];
    spA8 = camera->lookAt[1] - camera->pos[1];
    spA4 = camera->lookAt[2] - camera->pos[2];
    camera->rot[1] = atan2s(spAC, spA4);
    camera->rot[0] = atan2s(sqrtf((spAC * spAC) + (spA4 * spA4)), spA8);
    camera->rot[2] = 0;
}

void func_80017720(s32 playerId, UNUSED f32 arg1, s32 cameraId, s16 pathIndex) {
    Camera* camera = cameras + cameraId;
    UNUSED s32 pad;

    D_80164688[cameraId] = gTrackPositionFactor[playerId];
    gNearestPathPointByCameraId[cameraId] = gNearestPathPointByPlayerId[playerId] + 3;
    gNearestPathPointByCameraId[cameraId] = gNearestPathPointByCameraId[cameraId] % gPathCountByPathIndex[pathIndex];

    set_track_offset_position(gNearestPathPointByCameraId[cameraId], gTrackPositionFactor[playerId], pathIndex);

    D_801645F8[cameraId] = gOffsetPosition[0];
    D_80164618[cameraId] = (f32) gTrackPaths[pathIndex][gNearestPathPointByCameraId[cameraId]].posY;
    D_80164638[cameraId] = gOffsetPosition[2];

    D_80164658[cameraId] = gPlayers[playerId].speed;
    D_80164648[cameraId] = gPlayers[playerId].speed;

    camera->pos[0] = D_801645F8[cameraId];
    camera->pos[1] = D_80164618[cameraId];
    camera->pos[2] = D_80164638[cameraId];
}

void func_800178F4(Camera* camera, UNUSED Player* unusedPlayer, UNUSED s32 arg2, s32 cameraId) {
    s16 cameraWaypoint;
    s16 playerWaypoint;
    UNUSED f32 stackPadding0;
    f32 xdiff2;
    f32 ydiff2;
    f32 zdiff2;
    Player* player;
    f32 distance;
    f32 midX;
    f32 midY;
    f32 midZ;
    f32 xdiff;
    f32 ydiff;
    f32 zdiff;
    UNUSED f32 var_f2;
    UNUSED f32 var_f18;
    s16 waypoint1;
    s16 waypoint2;
    UNUSED f32 stackPadding1;
    UNUSED f32 var_f14;
    s32 playerId;
    s32 pathIndex;
    s32 waypointCount;

    playerId = camera->playerId;
    player = gPlayerOne;
    D_80164688[cameraId] = gTrackPositionFactor[playerId];
    D_80164648[cameraId] += ((D_80164658[cameraId] - D_80164648[cameraId]) / 2.0f);
    D_80163238 = playerId;
    pathIndex = gPathIndexByPlayerId[playerId];
    player += playerId;
    waypointCount = gPathCountByPathIndex[pathIndex];
    gNearestPathPointByCameraId[cameraId] =
        func_8000D33C(camera->pos[0], camera->pos[1], camera->pos[2], gNearestPathPointByCameraId[cameraId], pathIndex);
    playerWaypoint = (gNearestPathPointByPlayerId[playerId] + 3) % waypointCount;
    cameraWaypoint = gNearestPathPointByCameraId[cameraId];
    if (is_path_point_in_range(playerWaypoint, cameraWaypoint, 0x000DU, 1U, waypointCount) <= 0) {
        func_8001A348(cameraId, D_80164688[cameraId], D_80164680[cameraId]);
    } else {
        if ((cameraWaypoint < playerWaypoint) && ((playerWaypoint - cameraWaypoint) < 3)) {
            D_80164658[cameraId] = gPlayers[playerId].speed + 0.1;
        }
        if ((playerWaypoint < cameraWaypoint) && ((cameraWaypoint - playerWaypoint) < 3)) {
            D_80164658[cameraId] = gPlayers[playerId].speed - 0.1;
        }
        if (D_80164658[cameraId] > 10.0) {
            D_80164658[cameraId] = 10.0f;
        }
        if (D_80164658[cameraId] < 0.0) {
            D_80164658[cameraId] = 0.0f;
        }
    }
    waypoint1 = (gNearestPathPointByCameraId[cameraId] + 1) % waypointCount;
    waypoint2 = (gNearestPathPointByCameraId[cameraId] + 2) % waypointCount;
    set_track_offset_position(waypoint1, D_80164688[cameraId], pathIndex);
    midX = gOffsetPosition[0] * 0.5;
    midZ = gOffsetPosition[2] * 0.5;
    set_track_offset_position(waypoint2, D_80164688[cameraId], pathIndex);
    midX += gOffsetPosition[0] * 0.5;
    midZ += gOffsetPosition[2] * 0.5;
    midY = (gTrackPaths[pathIndex][waypoint1].posY + gTrackPaths[pathIndex][waypoint2].posY) / 2.0;
    xdiff = midX - D_801645F8[cameraId];
    ydiff = midY - D_80164618[cameraId];
    zdiff = midZ - D_80164638[cameraId];
    distance = sqrtf((xdiff * xdiff) + (ydiff * ydiff) + (zdiff * zdiff));
    if (distance != 0.0) {
        midX = D_801645F8[cameraId] + ((D_80164648[cameraId] * xdiff) / distance);
        midY = D_80164618[cameraId] + ((D_80164648[cameraId] * ydiff) / distance);
        midZ = D_80164638[cameraId] + ((D_80164648[cameraId] * zdiff) / distance);
    } else {
        midX = D_801645F8[cameraId];
        midY = D_80164618[cameraId];
        midZ = D_80164638[cameraId];
    }
    camera->pos[0] = midX;
    camera->pos[2] = midZ;
    camera->pos[1] = midY + 10.0;
    D_801645F8[cameraId] = midX;
    D_80164618[cameraId] = midY;
    D_80164638[cameraId] = midZ;
    camera->lookAt[0] = player->pos[0];
    camera->lookAt[1] = player->pos[1] + 6.0;
    camera->lookAt[2] = player->pos[2];
    func_80014D30(cameraId, pathIndex);
    xdiff2 = camera->lookAt[0] - camera->pos[0];
    ydiff2 = camera->lookAt[1] - camera->pos[1];
    zdiff2 = camera->lookAt[2] - camera->pos[2];
    camera->rot[1] = atan2s(xdiff2, zdiff2);
    camera->rot[0] = atan2s(sqrtf((xdiff2 * xdiff2) + (zdiff2 * zdiff2)), ydiff2);
    camera->rot[2] = 0;
}

void func_80017F10(s32 playerId, UNUSED f32 arg1, s32 cameraId, s16 pathIndex) {
    Camera* camera = cameras + cameraId;
    s32 test = gPathCountByPathIndex[pathIndex];

    D_80164688[cameraId] = gTrackPositionFactor[playerId];
    gNearestPathPointByCameraId[cameraId] = (gNearestPathPointByPlayerId[playerId] + test) - 2;
    gNearestPathPointByCameraId[cameraId] = gNearestPathPointByCameraId[cameraId] % test;

    set_track_offset_position(gNearestPathPointByCameraId[cameraId], gTrackPositionFactor[playerId], pathIndex);

    D_801645F8[cameraId] = gOffsetPosition[0];
    D_80164618[cameraId] = (f32) gTrackPaths[pathIndex][gNearestPathPointByCameraId[cameraId]].posY;
    D_80164638[cameraId] = gOffsetPosition[2];

    D_80164658[cameraId] = gPlayers[playerId].speed;
    D_80164648[cameraId] = gPlayers[playerId].speed;

    camera->pos[0] = D_801645F8[cameraId];
    camera->pos[1] = D_80164618[cameraId];
    camera->pos[2] = D_80164638[cameraId];
}

void func_800180F0(Camera* camera, UNUSED Player* unusedPlayer, UNUSED s32 arg2, s32 cameraId) {
    s16 cameraWaypoint;
    s16 playerWaypoint;
    UNUSED s32 stackPadding3;
    f32 sp94;
    f32 sp90;
    f32 sp8C;
    UNUSED s32 stackPadding4;
    f32 distance;
    f32 midX;
    f32 midY;
    f32 midZ;
    f32 xdiff;
    f32 ydiff;
    f32 zdiff;
    Player* player;
    s32 playerId;
    s16 waypoint1;
    s16 waypoint2;
    UNUSED f32 var_f2;
    UNUSED f32 var_f18;
    UNUSED f32 var_f14;
    s32 pathIndex;
    s32 waypointCount;

    playerId = camera->playerId;
    player = gPlayerOne;
    D_80164688[cameraId] = gTrackPositionFactor[playerId];
    D_80164648[cameraId] += ((D_80164658[cameraId] - D_80164648[cameraId]) * 0.5f);
    D_80163238 = playerId;
    pathIndex = gPathIndexByPlayerId[playerId];
    waypointCount = gPathCountByPathIndex[pathIndex];
    player += playerId;
    gNearestPathPointByCameraId[cameraId] =
        func_8000D33C(camera->pos[0], camera->pos[1], camera->pos[2], gNearestPathPointByCameraId[cameraId], pathIndex);
    playerWaypoint = ((gNearestPathPointByPlayerId[playerId] + waypointCount) - 2) % waypointCount;
    cameraWaypoint = gNearestPathPointByCameraId[cameraId];
    if (is_path_point_in_range(playerWaypoint, cameraWaypoint, 1U, 0x000AU, waypointCount) <= 0) {
        func_8001A348(cameraId, D_80164688[cameraId], D_80164680[cameraId]);
    } else {
        if ((cameraWaypoint < playerWaypoint) && ((playerWaypoint - cameraWaypoint) < 3)) {
            D_80164658[cameraId] = gPlayers[playerId].speed + 0.1;
        }
        if ((playerWaypoint < cameraWaypoint) && ((cameraWaypoint - playerWaypoint) < 3)) {
            D_80164658[cameraId] = gPlayers[playerId].speed - 0.1;
        }
        if (D_80164658[cameraId] > 10.0) {
            D_80164658[cameraId] = 10.0f;
        }
        if (D_80164658[cameraId] < 0.0) {
            D_80164658[cameraId] = 0.0f;
        }
    }
    waypoint1 = (gNearestPathPointByCameraId[cameraId] + 1) % waypointCount;
    waypoint2 = (gNearestPathPointByCameraId[cameraId] + 2) % waypointCount;
    set_track_offset_position(waypoint1, D_80164688[cameraId], pathIndex);
    midX = gOffsetPosition[0] * 0.5;
    midZ = gOffsetPosition[2] * 0.5;
    set_track_offset_position(waypoint2, D_80164688[cameraId], pathIndex);
    midX += gOffsetPosition[0] * 0.5;
    midZ += gOffsetPosition[2] * 0.5;
    midY = (gTrackPaths[pathIndex][waypoint1].posY + gTrackPaths[pathIndex][waypoint2].posY) / 2.0;
    xdiff = midX - D_801645F8[cameraId];
    ydiff = midY - D_80164618[cameraId];
    zdiff = midZ - D_80164638[cameraId];
    distance = sqrtf((xdiff * xdiff) + (ydiff * ydiff) + (zdiff * zdiff));
    if (distance != 0.0) {
        midX = D_801645F8[cameraId] + ((D_80164648[cameraId] * xdiff) / distance);
        midY = D_80164618[cameraId] + ((D_80164648[cameraId] * ydiff) / distance);
        midZ = D_80164638[cameraId] + ((D_80164648[cameraId] * zdiff) / distance);
    } else {
        midX = D_801645F8[cameraId];
        midY = D_80164618[cameraId];
        midZ = D_80164638[cameraId];
    }
    camera->pos[0] = midX;
    camera->pos[2] = midZ;
    camera->pos[1] = player->pos[1] + 10.0;
    D_801645F8[cameraId] = midX;
    D_80164618[cameraId] = midY;
    D_80164638[cameraId] = midZ;
    camera->lookAt[0] = player->pos[0];
    camera->lookAt[1] = player->pos[1] + 6.0;
    camera->lookAt[2] = player->pos[2];
    func_80014D30(cameraId, pathIndex);
    sp94 = camera->lookAt[0] - camera->pos[0];
    sp90 = camera->lookAt[1] - camera->pos[1];
    sp8C = camera->lookAt[2] - camera->pos[2];
    camera->rot[1] = atan2s(sp94, sp8C);
    camera->rot[0] = atan2s(sqrtf((sp94 * sp94) + (sp8C * sp8C)), sp90);
    camera->rot[2] = 0;
}

void func_80018718(s32 playerId, UNUSED f32 arg1, s32 cameraId, s16 pathIndex) {
    Camera* camera = cameras + cameraId;
    s32 test = gPathCountByPathIndex[pathIndex];

    D_80164688[cameraId] = gTrackPositionFactor[playerId];
    gNearestPathPointByCameraId[cameraId] = ((gNearestPathPointByPlayerId[playerId] + test) - 5) % test;

    calculate_track_offset_position(gNearestPathPointByCameraId[cameraId], gTrackPositionFactor[playerId], 60.0f,
                                    pathIndex);

    D_801645F8[cameraId] = gOffsetPosition[0];
    D_80164618[cameraId] = (f32) gTrackPaths[pathIndex][gNearestPathPointByCameraId[cameraId]].posY;
    D_80164638[cameraId] = gOffsetPosition[2];

    D_80164658[cameraId] = gPlayers[playerId].speed;
    D_80164648[cameraId] = gPlayers[playerId].speed;
    D_8016448C = 1;
    camera->pos[0] = D_801645F8[cameraId];
    camera->pos[1] = D_80164618[cameraId];
    camera->pos[2] = D_80164638[cameraId];
}

void func_800188F4(Camera* camera, UNUSED Player* unusePlayer, UNUSED s32 arg2, s32 cameraId) {
    s16 cameraWaypoint;
    s16 playerWaypoint;
    UNUSED s32 stackPadding0;
    f32 spAC;
    f32 spA8;
    f32 spA4;
    Player* player;
    f32 distance;
    f32 midX;
    f32 midY;
    f32 midZ;
    f32 xdiff;
    f32 ydiff;
    f32 zdiff;
    UNUSED f32 sp64;
    UNUSED f32 sp60;
    s16 waypoint1;
    s16 waypoint2;
    UNUSED f32 sp5C;
    f32 temp_f2_4;
    s32 playerId;
    s32 pathIndex;
    s32 waypointCount;

    player = gPlayerOne;
    playerId = camera->playerId;
    pathIndex = gPathIndexByPlayerId[playerId];
    waypointCount = gPathCountByPathIndex[pathIndex];
    D_80164648[cameraId] = gPlayers[playerId].speed;
    if (D_8016448C == 0) {
        if (gNearestPathPointByCameraId[cameraId] ==
            (((gNearestPathPointByPlayerId[playerId] + waypointCount) - 6) % waypointCount)) {
            D_8016448C = 1;
        }
        if (D_80164688[cameraId] < (gTrackPositionFactor[playerId] - 0.2)) {
            D_80164648[cameraId] = gPlayers[playerId].speed * 0.7;
        }
        if ((gTrackPositionFactor[playerId] - 0.5) < D_80164688[cameraId]) {
            D_80164688[cameraId] -= 0.01;
        }
        if (D_80164688[cameraId] < -0.9) {
            D_80164688[cameraId] = -0.9f;
            D_80164648[cameraId] = gPlayers[playerId].speed * 0.8;
        }
    } else {
        if (gNearestPathPointByCameraId[cameraId] == ((gNearestPathPointByPlayerId[playerId] + 6) % waypointCount)) {
            D_8016448C = 0;
        }
        if ((gTrackPositionFactor[playerId] + 0.2) < D_80164688[cameraId]) {
            D_80164648[cameraId] = gPlayers[playerId].speed * 1.3;
        }
        if (D_80164688[cameraId] < (gTrackPositionFactor[playerId] + 0.5)) {
            D_80164688[cameraId] += 0.01;
        }
        if (D_80164688[cameraId] > 0.9) {
            D_80164688[cameraId] = 0.9f;
            D_80164648[cameraId] = gPlayers[playerId].speed * 1.2;
        }
    }
    D_80163238 = playerId;
    player += playerId;
    gNearestPathPointByCameraId[cameraId] =
        func_8000D33C(camera->pos[0], camera->pos[1], camera->pos[2], gNearestPathPointByCameraId[cameraId], pathIndex);
    playerWaypoint = gNearestPathPointByPlayerId[playerId];
    cameraWaypoint = gNearestPathPointByCameraId[cameraId];
    if (is_path_point_in_range(playerWaypoint, cameraWaypoint, 0x000FU, 0x000FU, waypointCount) <= 0) {
        func_8001A348(cameraId, D_80164688[cameraId], D_80164680[cameraId]);
    }
    waypoint1 = (gNearestPathPointByCameraId[cameraId] + 1) % waypointCount;
    waypoint2 = (gNearestPathPointByCameraId[cameraId] + 2) % waypointCount;
    calculate_track_offset_position(waypoint1, D_80164688[cameraId], 60.0f, pathIndex);
    midX = gOffsetPosition[0] * 0.5;
    midZ = gOffsetPosition[2] * 0.5;
    calculate_track_offset_position(waypoint2, D_80164688[cameraId], 60.0f, pathIndex);
    midX += gOffsetPosition[0] * 0.5;
    midZ += gOffsetPosition[2] * 0.5;
    midY = (gTrackPaths[pathIndex][waypoint1].posY + gTrackPaths[pathIndex][waypoint2].posY) / 2.0;
    xdiff = midX - D_801645F8[cameraId];
    ydiff = midY - D_80164618[cameraId];
    zdiff = midZ - D_80164638[cameraId];
    distance = sqrtf((xdiff * xdiff) + (ydiff * ydiff) + (zdiff * zdiff));
    if (distance != 0.0) {
        midX = D_801645F8[cameraId] + ((D_80164648[cameraId] * xdiff) / distance);
        midY = D_80164618[cameraId] + ((D_80164648[cameraId] * ydiff) / distance);
        midZ = D_80164638[cameraId] + ((D_80164648[cameraId] * zdiff) / distance);
    } else {
        midX = D_801645F8[cameraId];
        midY = D_80164618[cameraId];
        midZ = D_80164638[cameraId];
    }
    camera->pos[0] = midX;
    camera->pos[2] = midZ;
    temp_f2_4 = spawn_actor_on_surface(midX, midY + 30.0, midZ);
    if ((temp_f2_4 < (midY - 20.0)) || (temp_f2_4 >= 3000.0)) {
        camera->pos[1] = midY + 10.0;
    } else {
        camera->pos[1] = temp_f2_4 + 8.0;
    }
    D_801645F8[cameraId] = midX;
    D_80164618[cameraId] = midY;
    D_80164638[cameraId] = midZ;
    camera->lookAt[0] = player->pos[0];
    camera->lookAt[1] = player->pos[1] + 6.0;
    camera->lookAt[2] = player->pos[2];
    func_80014D30(cameraId, pathIndex);
    spAC = camera->lookAt[0] - camera->pos[0];
    spA8 = camera->lookAt[1] - camera->pos[1];
    spA4 = camera->lookAt[2] - camera->pos[2];
    camera->rot[1] = atan2s(spAC, spA4);
    camera->rot[0] = atan2s(sqrtf((spAC * spAC) + (spA4 * spA4)), spA8);
    camera->rot[2] = 0;
}

void func_80019118(s32 playerId, f32 arg1, s32 cameraId, UNUSED s16 pathIndex) {
    Camera* camera = cameras + cameraId;
    s32 test = gPathCountByPathIndex[0];
    f32 temp_f12;
    f32 temp_f2;

    D_80164688[cameraId] = arg1;
    gNearestPathPointByCameraId[cameraId] = gNearestPathPointByPlayerId[playerId] + 12;
    gNearestPathPointByCameraId[cameraId] = gNearestPathPointByCameraId[cameraId] % test;

    set_track_offset_position(gNearestPathPointByCameraId[cameraId], arg1, 0);
    D_801645F8[cameraId] = gOffsetPosition[0];
    D_80164638[cameraId] = gOffsetPosition[2];
    temp_f2 = (f32) gTrackPaths[0][gNearestPathPointByCameraId[cameraId]].posY;

    temp_f12 = spawn_actor_on_surface(gOffsetPosition[0], (f32) (temp_f2 + 30.0), gOffsetPosition[2]);

    if ((temp_f12 < (temp_f2 - 20.0)) || (temp_f12 >= 3000.0)) {
        D_80164618[cameraId] = (f32) (temp_f2 + 10.0);
    } else {
        D_80164618[cameraId] = (f32) (temp_f12 + 10.0);
    }
    D_80164648[cameraId] = 0.0f;
    camera->pos[0] = D_801645F8[cameraId];
    camera->pos[1] = D_80164618[cameraId];
    camera->pos[2] = D_80164638[cameraId];
}

void func_8001933C(Camera* camera, UNUSED Player* playerArg, UNUSED s32 arg2, s32 cameraId) {
    s16 cameraWaypoint;
    s16 playerWaypoint;
    UNUSED s32 pad;
    f32 xdiff;
    f32 ydiff;
    f32 zdiff;
    UNUSED s32 pad2;
    s32 playerId;
    UNUSED s32 pad3[10];
    Player* player;
    TrackPathPoint* waypoint;
    s32 pathIndex;
    s32 waypointCount;

    playerId = camera->playerId;
    pathIndex = gPathIndexByPlayerId[playerId];
    player = gPlayerOne;
    player += playerId;
    waypointCount = gPathCountByPathIndex[pathIndex];
    gNearestPathPointByCameraId[cameraId] =
        func_8000D33C(camera->pos[0], camera->pos[1], camera->pos[2], gNearestPathPointByCameraId[cameraId], 0);
    playerWaypoint = gNearestPathPointByPlayerId[playerId];
    cameraWaypoint = gNearestPathPointByCameraId[cameraId];
    if (is_path_point_in_range(playerWaypoint, cameraWaypoint, 0x0032U, 0x0014U, waypointCount) <= 0) {
        func_8001A348(cameraId, D_80164688[cameraId], D_80164680[cameraId]);
    } else {
        if ((gTrackPositionFactor[playerId] < -0.5) && ((f64) D_80164688[cameraId] < -0.5)) {
            func_8001A348(cameraId, 1.0f, 0x0000000D);
        } else if ((gTrackPositionFactor[playerId] > 0.5) && ((f64) D_80164688[cameraId] > 0.5)) {
            func_8001A348(cameraId, -1.0f, 0x0000000C);
        }
    }
    camera->pos[0] = D_801645F8[cameraId];
    camera->pos[1] = D_80164618[cameraId];
    camera->pos[2] = D_80164638[cameraId];
    waypoint = &gTrackPaths[pathIndex][cameraWaypoint];
    camera->lookAt[0] = (player->pos[0] * 0.8) + (0.2 * waypoint->posX);
    camera->lookAt[1] = (player->pos[1] * 0.8) + (0.2 * waypoint->posY);
    camera->lookAt[2] = (player->pos[2] * 0.8) + (0.2 * waypoint->posZ);
    func_80014D30(cameraId, pathIndex);
    xdiff = camera->lookAt[0] - camera->pos[0];
    ydiff = camera->lookAt[1] - camera->pos[1];
    zdiff = camera->lookAt[2] - camera->pos[2];
    camera->rot[1] = atan2s(xdiff, zdiff);
    camera->rot[0] = atan2s(sqrtf((xdiff * xdiff) + (zdiff * zdiff)), ydiff);
    camera->rot[2] = 0;
}

void func_8001968C(void) {
    D_80164678[0] = 3;
}

void func_8001969C(UNUSED s32 playerId, UNUSED f32 arg1, s32 cameraId, UNUSED s16 pathIndex) {
    TrackPathPoint* waypoint;

    gNearestPathPointByCameraId[cameraId] = gPathCountByPathIndex[0] - 18;

    waypoint = &gTrackPaths[0][gNearestPathPointByCameraId[cameraId]];

    D_801645F8[cameraId] = waypoint->posX;
    //! @bug Adding an (f32) cast changes asm, why?
    D_80164618[cameraId] = waypoint->posY + 10.0;
    D_80164638[cameraId] = waypoint->posZ;
    D_80164648[cameraId] = 0.0f;
    D_80164678[cameraId] = 0;
}

void func_80019760(Camera* camera, UNUSED Player* player, UNUSED s32 arg2, s32 cameraId) {
    UNUSED s32 pad[2];
    f32 xdiff;
    f32 ydiff;
    f32 zdiff;
    TrackPathPoint* temp_v1;

    camera->pos[0] = D_801645F8[cameraId];
    camera->pos[1] = D_80164618[cameraId];
    camera->pos[2] = D_80164638[cameraId];
    temp_v1 = &(*gTrackPaths)[gNearestPathPointByCameraId[cameraId]];
    camera->lookAt[0] = (f32) temp_v1->posX;
    camera->lookAt[1] = (f32) temp_v1->posY;
    camera->lookAt[2] = (f32) temp_v1->posZ;
    func_80014D30(cameraId, 0);
    xdiff = camera->lookAt[0] - camera->pos[0];
    ydiff = camera->lookAt[1] - camera->pos[1];
    zdiff = camera->lookAt[2] - camera->pos[2];
    camera->rot[1] = atan2s(xdiff, zdiff);
    camera->rot[0] = atan2s(sqrtf((xdiff * xdiff) + (zdiff * zdiff)), ydiff);
    camera->rot[2] = 0;
}

void func_80019890(s32 playerId, s32 cameraId) {
    s32 pathIndex;
    Camera* camera = camera1;
    camera += cameraId;
    camera->playerId = playerId;

    D_801646C0[cameraId] = 0;
    pathIndex = gPathIndexByPlayerId[playerId];

    switch (D_80164680[cameraId]) {
        case 0:
            func_80015314(playerId, 0.0f, cameraId);
            break;
        case 2:
            func_80015544(playerId, -1.0f, cameraId, pathIndex);
            break;
        case 3:
            func_80015544(playerId, 1.0f, cameraId, pathIndex);
            break;
        case 6:
            func_80015A9C(playerId, -0.6f, cameraId, (s16) pathIndex);
            break;
        case 7:
            func_80015A9C(playerId, 0.6f, cameraId, (s16) pathIndex);
            break;
        case 4:
            func_800162CC(playerId, -1.0f, cameraId, (s16) pathIndex);
            break;
        case 5:
            func_800162CC(playerId, 1.0f, cameraId, (s16) pathIndex);
            break;
        case 9:
            func_80016C3C(playerId, 0.0f, cameraId);
            break;
        case 1:
            func_80017720(playerId, 0.0f, cameraId, (s16) pathIndex);
            break;
        case 14:
            func_80017F10(playerId, 0.0f, cameraId, (s16) pathIndex);
            break;
        case 8:
            func_80018718(playerId, 0.0f, cameraId, (s16) pathIndex);
            break;
        case 12:
            func_80019118(playerId, -1.0f, cameraId, (s16) pathIndex);
            break;
        case 13:
            func_80019118(playerId, 1.0f, cameraId, (s16) pathIndex);
            break;
        case 15:
            func_8001969C(playerId, -1.0f, cameraId, (s16) pathIndex);
            break;
        case 16:
            func_8001969C(playerId, 1.0f, cameraId, (s16) pathIndex);
            break;
        default:
            func_80015314(playerId, 0.0f, cameraId);
            break;
    }
    gNearestPathPointByCameraId[cameraId] =
        func_8000BD94(camera->pos[0], camera->pos[1], camera->pos[2], (s32) pathIndex);
    if ((s16) D_80164680[cameraId] == 9) {
        D_80163DD8[cameraId] = (s32) pathIndex;
    }
}

void func_80019B50(s32 cameraIndex, u16 arg1) {
    u16 var_v0;

    var_v0 = D_801646C0[cameraIndex];
    if (var_v0 < arg1) {
        if ((arg1 - var_v0) < 0x8000) {
            var_v0 += 0x5A;
            if (arg1 < var_v0) {
                var_v0 = arg1;
            }
            if (var_v0 < 0x5A) {
                var_v0 = 0;
            }
        } else if ((arg1 - var_v0) >= 0x8000) {
            var_v0 -= 0x5A;
        }
    } else if (arg1 < var_v0) {
        if ((var_v0 - arg1) < 0x8000) {
            var_v0 -= 0x5A;
            if (var_v0 < arg1) {
                var_v0 = arg1;
            }
            if (var_v0 >= 0xFFA6) {
                var_v0 = 0;
            }
        } else if ((var_v0 - arg1) >= 0x8000) {
            var_v0 += 0x5A;
            if (!cameraIndex) {} // wtf?
        }
    }
    D_801646C0[cameraIndex] = (s16) var_v0;
}

void func_80019C50(s32 arg0) {
    switch (D_80164678[arg0]) {
        case 0:
            if (D_80164608[arg0] == 1) {
                D_80164678[arg0] = 1;
                func_800C9060(arg0, SOUND_ARG_LOAD(0x19, 0x00, 0x90, 0x4F));
                D_80164670[arg0] = D_80164678[arg0];
            }
            break;
        case 1:
            if (D_80164608[arg0] == 1) {
                D_80164678[arg0] = 0;
                func_800C9060(arg0, SOUND_ARG_LOAD(0x19, 0x00, 0x90, 0x50));
                D_80164670[arg0] = D_80164678[arg0];
            }
            break;
    }
}

void func_80019D2C(Camera* camera, Player* player, s32 arg2) {
    s32 playerId;
    s32 nearestWaypoint;

    playerId = camera->playerId;
    if ((gIncrementUpdatePlayer != 0) && (IsLuigiRaceway())) {
        calculate_camera_up_vector(camera, arg2);
        nearestWaypoint = gNearestPathPointByPlayerId[playerId];
        if (((nearestWaypoint >= 0x65) && (nearestWaypoint < 0xFA)) ||
            ((nearestWaypoint >= 0x1AF) && (nearestWaypoint < 0x226))) {
            func_80019B50(arg2, (player->unk_206 * 2));
        } else {
            func_80019B50(arg2, 0U);
        }
    }
}

void func_80019DE4(void) {
    D_801646CC = 1;
}

void func_80019DF4(void) {
    s32 i;
    s32 playerId = gGPCurrentRacePlayerIdByRank[0];
    // clang-format off
    // Has to be on a single line to match. Because IDO hates you :)
    for (i = 0; i < 4; i++) { D_80164670[i] = D_80164678[i]; }
    // clang-format on
    camera1->playerId = playerId;
    D_80164678[0] = 1;
    D_801646CC = 2;
}

void func_80019E58(void) {
    D_80164680[0] = 1;
    func_80019890(0, 0);
    D_80164670[0] = D_80164678[0];
    D_80164678[0] = 1;
    D_80164680[1] = 9;
    func_80019890(0, 1);
    D_80164670[1] = D_80164678[1];
    D_80164678[1] = 0;
}

void func_80019ED0(void) {
    s32 i;

    for (i = 0; i < 4; i++) {
        D_80164670[i] = D_80164678[i];
    }

    gGPCurrentRacePlayerIdByRank[0] = (s16) gPlayerWinningIndex;

    camera1->playerId = (s16) gPlayerWinningIndex;

    for (i = 0; i < 4; i++) {
        D_80164680[i] = 0;
        func_80015314(gPlayerWinningIndex, 0, i);
        D_80164678[i] = 1;
    }
}

void func_80019FB4(s32 cameraId) {
    struct Controller* controller;

    controller = &gControllerOne[cameraId];
    if (controller->buttonPressed & 2) {
        D_801645D0[cameraId] += 1;
    } else {
        D_801645D0[cameraId] = 0;
    }
    if (controller->buttonPressed & 4) {
        D_801645E8[cameraId] += 1;
    } else {
        D_801645E8[cameraId] = 0;
    }
    if (controller->buttonPressed & 8) {
        D_80164608[cameraId] += 1;
    } else {
        D_80164608[cameraId] = 0;
    }
    if (controller->buttonPressed & 1) {
        D_80164628[cameraId] += 1;
    } else {
        D_80164628[cameraId] = 0;
    }
}

void func_8001A0A4(UNUSED u16* arg0, UNUSED Camera* arg1, UNUSED Player* arg2, UNUSED s8 arg3, s32 arg4) {
    func_80019FB4(arg4);
    func_80019C50(arg4);
}

void func_8001A0DC(u16* arg0, Camera* arg1, Player* arg2, s8 arg3, s32 arg4) {
    func_8001A0A4(arg0, arg1, arg2, arg3, arg4);
    func_80019D2C(arg1, arg2, arg4);
}

void func_8001A124(s32 arg0, s32 arg1) {
    switch (gGPCurrentRaceRankByPlayerId[arg0]) { /* irregular */
        case 0:
            if (random_int(0x0064U) < 0x32) {
                D_80164680[arg1] = 0x000C;
            } else {
                D_80164680[arg1] = 0x000D;
            }
            func_800CA270();
            break;
        case 1:
        case 2:
        case 3:
            D_80164680[arg1] = 8;
            break;
        default:
            if (random_int(0x0064U) < 0x32) {
                D_80164680[arg1] = 0x000F;
            } else {
                D_80164680[arg1] = 0x0010;
            }
            break;
    }
}

void func_8001A220(UNUSED s32 arg0, s32 cameraId) {
    switch (random_int(6)) {
        case 0:
            D_80164680[cameraId] = 4;
            break;
        case 1:
            D_80164680[cameraId] = 5;
            break;
        case 2:
            D_80164680[cameraId] = 6;
            break;
        case 3:
            D_80164680[cameraId] = 7;
            break;
        case 4:
            D_80164680[cameraId] = 8;
            break;
        case 5:
            D_80164680[cameraId] = 1;
            break;
        default:
            D_80164680[cameraId] = 8;
            break;
    }
}

s32 func_8001A310(s32 waypoint, s32 arg1) {
    if ((IsBowsersCastle()) && (arg1 != 0) && (waypoint >= 0xE7) && (waypoint < 0x1C2)) {
        arg1 = 0;
    }
    return arg1;
}

void func_8001A348(s32 cameraId, f32 arg1, s32 arg2) {
    UNUSED s32 pad;
    s32 playerId;

    playerId = cameras[cameraId].playerId;
    D_80164688[cameraId] = arg1;
    D_80164680[cameraId] = func_8001A310((s32) gNearestPathPointByCameraId[cameraId], arg2);
    func_80019890(playerId, cameraId);
}

void func_8001A3D8(s32 arg0, f32 arg1, s32 arg2) {
    s32 playerId;

    playerId = cameras[arg0].playerId;
    D_80164688[arg0] = arg1;
    if (arg2 != D_80164680[arg0]) {
        D_80164680[arg0] = arg2;
        func_80019890(playerId, arg0);
    }
}

void func_8001A450(s32 playerId, s32 arg1, s32 arg2) {
    s32 temp_v1;
    s16 waypoint;
    s32 temp_v0;

    if (!(gPlayers[playerId].effects & 0x1000000C)) {
        temp_v1 = D_80164680[arg1];
        waypoint = gNearestPathPointByCameraId[arg1];
        temp_v0 = func_8001A310(waypoint, (temp_v1 + 1) % 10);
        if ((temp_v0 != temp_v1) || (arg2 != playerId)) {
            D_80164680[arg1] = temp_v0;
            func_80019890(arg2, arg1);
        }
    }
}

void func_8001A518(s32 arg0, s32 arg1, s32 arg2) {
    switch (arg1) { /* irregular */
        case 0:
            D_80164680[arg2] = 1;
            break;
        case 1:
        case 2:
        case 3:
            func_8001A450(arg0, arg2, arg0);
            break;
        default:
            D_80164680[arg2] = 0;
            break;
    }
}

void func_8001A588(UNUSED u16* localD_80152300, Camera* camera, Player* player, s8 index, s32 cameraIndex) {
    s32 var_v1;
    UnkStruct_46D0* temp_v0_4;
    s32 sp44;
    s32 playerId;
    playerId = camera->playerId;

    if (gModeSelection == TIME_TRIALS) {
        playerId = 0;
    }
    func_80019FB4(cameraIndex);

    if ((s32) (camera->pos[0] * 10.0) == (s32) ((f64) camera->lookAt[0] * 10.0)) {

        if ((s32) (camera->pos[2] * 10.0) == (s32) ((f64) camera->lookAt[2] * 10.0)) {
            camera->pos[0] = (f32) (camera->pos[0] + 100.0);
            camera->pos[2] = (f32) (camera->pos[2] + 100.0);
        }
    }
    if ((gModeSelection != BATTLE) && (D_80164680[cameraIndex] == -1) && (player->type & 0x800) &&
        ((u16) D_801646CC == 0) && (D_801646C8 == 0)) {
        if (gModeSelection == VERSUS) {
            func_8001A220(playerId, cameraIndex);
        } else {
            func_8001A124((s32) playerId, cameraIndex);
        }
        func_80019890((s32) playerId, cameraIndex);
    }

    if ((D_80164680[cameraIndex] == 14) || (D_80164680[cameraIndex] == 0)) {
        func_80019D2C(camera, player, cameraIndex);
    } else {
        D_801646C0[cameraIndex] = 0;
        calculate_camera_up_vector(camera, cameraIndex);
    }
    switch ((u16) D_801646CC) {
        case 1:
            D_801646C8 += 1;
            if (D_801646C8 >= 501) {
                D_801646C8 = 0;
            }
            if ((cameraIndex == 0) && (((D_801646C8 == 10)) || (D_801646C8 == 11))) {
                func_8001A518((s32) playerId, gGPCurrentRaceRankByPlayerId[playerId], 0);
            }
            if ((gModeSelection != TIME_TRIALS) && (cameraIndex == 1) &&
                (((D_801646C8 == 260)) || (D_801646C8 == 261))) {

                var_v1 = 0;
                if (gPlayerCount == 2) {
                    func_8001A518((s32) playerId, gGPCurrentRaceRankByPlayerId[playerId], 1);
                } else {
                    sp44 = (s32) playerId;
                    while (var_v1 != 8) {
                        playerId += 1;
                        var_v1 += 1;
                        if (playerId >= 8) {
                            playerId = 1;
                        }
                        if ((!(gPlayers[playerId].unk_0CA & 2) && !(gPlayers[playerId].unk_0CA & 8))) {
                            break;
                        }
                    }
                    func_8001A450(sp44, cameraIndex, (s32) playerId);
                }
            }
            break;
        case 2:
            D_801646C8 += 1;
            if (D_801646C8 > 250) {
                D_801646C8 = 0;
            }
            if ((cameraIndex == 0) && (D_801646C8 == 10)) {
                func_8001A450((s32) playerId, cameraIndex, (s32) playerId);
            }
            break;
        default:
            temp_v0_4 = &D_801646D0[cameraIndex];
            if (temp_v0_4->unk0 == (s16) 1) {
                playerId = temp_v0_4->unk4;
                temp_v0_4->unk0 = 0;
                cameras[cameraIndex].playerId = playerId;
                func_8001A3D8(cameraIndex, 0.0f, (s32) temp_v0_4->unk2);
            }
            break;
    }
    func_80019C50(cameraIndex);
    switch (D_80164680[cameraIndex]) {
        case 0:
            func_80015390(camera, player, index);
            break;
        case 2:
        case 3:
            func_8001577C(camera, player, index, cameraIndex);
            break;
        case 6:
        case 7:
            func_80015C94(camera, player, index, cameraIndex);
            break;
        case 4:
        case 5:
            func_80016494(camera, player, index, cameraIndex);
            break;
        case 9:
            func_80017054(camera, player, index, cameraIndex);
            break;
        case 1:
            func_800178F4(camera, player, index, cameraIndex);
            break;
        case 14:
            func_800180F0(camera, player, index, cameraIndex);
            break;
        case 8:
            func_800188F4(camera, player, index, cameraIndex);
            break;
        case 12:
        case 13:
            func_8001933C(camera, player, index, cameraIndex);
            break;
        case 15:
        case 16:
            func_80019760(camera, player, index, cameraIndex);
            break;
        default:
            func_80015390(camera, player, index);
            break;
    }
}

void func_8001AAAC(s16 arg0, s16 arg1, s16 arg2) {
    if (D_801646D0[arg0].unk0 == 0) {
        D_801646D0[arg0].unk0 = 1;
        D_801646D0[arg0].unk2 = arg1;
        D_801646D0[arg0].unk4 = arg2;
    }
}

void func_8001AB00(void) {
    s32 var_v1;

    for (var_v1 = 0; var_v1 < NUM_PLAYERS; var_v1++) {
        cpu_ItemStrategy[var_v1].branch = CPU_STRATEGY_WAIT_NEXT_ITEM;
        cpu_ItemStrategy[var_v1].timer = 0;
        cpu_ItemStrategy[var_v1].actorIndex = -1;
        cpu_ItemStrategy[var_v1].numItemUse = 0;
        cpu_ItemStrategy[var_v1].numDroppedBananaBunch = 0;
    }
}

void cpu_decisions_branch_item(UNUSED s32 playerId, s16* branch, s32 itemId) {
    s32 value = -1;
    switch (itemId) {
        case ITEM_FAKE_ITEM_BOX:
            value = CPU_STRATEGY_ITEM_FAKE_ITEM_BOX;
            break;
        case ITEM_BOO:
            value = CPU_STRATEGY_ITEM_BOO;
            break;
        case ITEM_BANANA:
            value = CPU_STRATEGY_ITEM_BANANA;
            break;
        case ITEM_THUNDERBOLT:
            value = CPU_STRATEGY_ITEM_THUNDERBOLT;
            break;
        case ITEM_STAR:
            value = CPU_STRATEGY_ITEM_STAR;
            break;
        case ITEM_MUSHROOM:
            value = CPU_STRATEGY_ITEM_MUSHROOM;
            break;
        case ITEM_DOUBLE_MUSHROOM:
            break;
        case ITEM_TRIPLE_MUSHROOM:
            break;
        case ITEM_SUPER_MUSHROOM:
            break;
    }

    if (CVarGetInteger("gHarderCPU", 0) == 1) {
        switch (itemId) {
            case ITEM_BANANA_BUNCH:
                value = CPU_STRATEGY_ITEM_BANANA_BUNCH;
                break;
            case ITEM_BLUE_SPINY_SHELL:
                value = CPU_STRATEGY_ITEM_BLUE_SPINY_SHELL;
                break;
            case ITEM_GREEN_SHELL:
                value = CPU_STRATEGY_ITEM_GREEN_SHELL;
                break;
            case ITEM_RED_SHELL:
                value = CPU_STRATEGY_ITEM_RED_SHELL;
                break;
            // case ITEM_TRIPLE_GREEN_SHELL:
            //     value = CPU_STRATEGY_ITEM_TRIPLE_GREEN_SHELL;
            //     break;
            // case ITEM_TRIPLE_RED_SHELL:
            //     value = CPU_STRATEGY_ITEM_TRIPLE_RED_SHELL;
            //     break;
            case ITEM_DOUBLE_MUSHROOM:
                value = CPU_STRATEGY_ITEM_DOUBLE_MUSHROOM;
                break;
            case ITEM_TRIPLE_MUSHROOM:
                value = CPU_STRATEGY_ITEM_TRIPLE_MUSHROOM;
                break;
            case ITEM_SUPER_MUSHROOM:
                value = CPU_STRATEGY_ITEM_SUPER_MUSHROOM;
                break;
        }
    }

    if (value >= 0) {
        *branch = value;
    }
}

void func_8001ABE0(UNUSED s32 arg0, UNUSED CpuItemStrategyData* arg1) {
}

void clear_expired_strategies(CpuItemStrategyData* arg0) {
    if ((arg0->actorIndex < 0) || (arg0->actorIndex >= 0x64)) {
        arg0->branch = CPU_STRATEGY_WAIT_NEXT_ITEM;
        arg0->timer = 0;
    }
}

#ifdef NON_MATCHING
// By the looks of this function is probably something like `cpu_use_item_strategy`
// The use of several different actor types might make getting a match hard(er),
// might have to get creative/ugly with just a single generic `Actor` variable.
// https://decomp.me/scratch/FOlbG
void cpu_use_item_strategy(s32 playerId) {
    bool isNoProblem;
    Player* player;
    TrackPathPoint* pathPoint;
    CpuItemStrategyData* temp_s0;
    struct Actor* actor;
    struct ShellActor* shell;
    struct BananaActor* banana;
    struct FakeItemBox* fakeItemBox;
    struct BananaBunchParent* bananaBunchParent;

    player = &gPlayerOne[playerId];
    if (((gModeSelection != ((s32) 1)) && (((u16) D_801646CC) != ((u16) 1))) &&
        (!(player->type & PLAYER_CINEMATIC_MODE))) {
        temp_s0 = &cpu_ItemStrategy[playerId];
        switch (temp_s0->branch) {
            case CPU_STRATEGY_WAIT_NEXT_ITEM:
                temp_s0->actorIndex = -1;
                if (CVarGetInteger("gHarderCPU", 0) == 1) {
                    if (((gNumPathPointsTraversed[playerId] + (playerId * 0x14) + 0x64) % 0x8 == 0) &&
                        (temp_s0->timer >= 0x200)) {
                        cpu_decisions_branch_item(playerId, &temp_s0->branch,
                                                  hard_cpu_gen_random_item((s16) gLapCountByPlayerId[playerId],
                                                                        gGPCurrentRaceRankByPlayerId[playerId]));
                    }
                }
                if ((((playerId * 0x14) + 0x64) < gNumPathPointsTraversed[playerId]) && (temp_s0->timer >= 0x259) &&
                    (temp_s0->numItemUse < 3) && (gLapCountByPlayerId[playerId] < 3)) {
                    cpu_decisions_branch_item(playerId, &temp_s0->branch,
                                              cpu_gen_random_item((s16) gLapCountByPlayerId[playerId],
                                                                  gGPCurrentRaceRankByPlayerId[playerId]));
                } else {
                    func_8001ABE0(playerId, temp_s0);
                }
                break;

            case CPU_STRATEGY_ITEM_BANANA:
                // never true
                if ((gLapCountByPlayerId[playerId] > 0) &&
                    (gGPCurrentRaceRankByPlayerId[gBestRankedHumanPlayer] > gGPCurrentRaceRankByPlayerId[playerId]) &&
                    (gGPCurrentRaceRankByPlayerId[gBestRankedHumanPlayer] == FIRST_PLACE)) {
                    switch (player->characterId) {
                        case DK:
                            if (is_path_point_in_range(gNearestPathPointByPlayerId[playerId],
                                                       gNearestPathPointByPlayerId[gBestRankedHumanPlayer], 0x0028U, 2U,
                                                       (u16) ((s32) gSelectedPathCount)) > 0) {
                                temp_s0->branch = CPU_STRATEGY_THROW_BANANA;
                            }
                            break;

                        case PEACH:
                            if (is_path_point_in_range(gNearestPathPointByPlayerId[playerId],
                                                       gNearestPathPointByPlayerId[gBestRankedHumanPlayer], 4U, 2U,
                                                       (u16) ((s32) gSelectedPathCount)) > 0) {
                                temp_s0->branch = CPU_STRATEGY_THROW_BANANA;
                            }
                            break;

                        default:
                            if (is_path_point_in_range(gNearestPathPointByPlayerId[playerId],
                                                       gNearestPathPointByPlayerId[gBestRankedHumanPlayer], 0x000AU, 2U,
                                                       (u16) ((s32) gSelectedPathCount)) > 0) {
                                temp_s0->branch = CPU_STRATEGY_THROW_BANANA;
                            }
                            break;
                    }
                } else if (temp_s0->branch == CPU_STRATEGY_ITEM_BANANA) {
                    temp_s0->actorIndex = use_banana_item(player);
                    if ((temp_s0->actorIndex >= 0) && (temp_s0->actorIndex < 0x64)) {
                        player->soundEffects |= HOLD_BANANA_SOUND_EFFECT;
                        temp_s0->branch = CPU_STRATEGY_HOLD_BANANA;
                        temp_s0->timer = 0;
                        temp_s0->numItemUse += 1;
                        temp_s0->timeBeforeThrow = (random_int(3U) * 0x14) + 0xA;
                    } else {
                        temp_s0->branch = CPU_STRATEGY_WAIT_NEXT_ITEM;
                        temp_s0->timer = 0;
                    }
                }
                break;

            case CPU_STRATEGY_HOLD_BANANA:
                banana = (struct BananaActor*) GET_ACTOR(temp_s0->actorIndex);
                if ((!(banana->flags & 0x8000)) || (banana->type != ACTOR_BANANA) || (banana->state != HELD_BANANA) ||
                    (playerId != banana->playerId)) {
                    temp_s0->branch = CPU_STRATEGY_WAIT_NEXT_ITEM;
                    temp_s0->timer = 0;
                    player->soundEffects &= ~HOLD_BANANA_SOUND_EFFECT;
                } else if (temp_s0->timeBeforeThrow < temp_s0->timer) {
                    temp_s0->branch = CPU_STRATEGY_DROP_BANANA;
                }
                break;

            case CPU_STRATEGY_DROP_BANANA:
                banana = (struct BananaActor*) GET_ACTOR(temp_s0->actorIndex);
                if ((((!(banana->flags & 0x8000)) || (banana->type != ACTOR_BANANA)) ||
                     (banana->state != HELD_BANANA)) ||
                    (playerId != banana->playerId)) {
                    if (playerId != banana->playerId) {}
                } else {
                    banana->state = DROPPED_BANANA;
                    banana->velocity[0] = 0.0f;
                    banana->velocity[1] = 0.0f;
                    banana->velocity[2] = 0.0f;
                    if (D_801631E0[playerId] == ((u16) true)) {
                        banana->pos[1] = spawn_actor_on_surface(player->pos[0], (f32) (((f64) player->pos[1]) + 30.0),
                                                                player->pos[2]) +
                                         (banana->boundingBoxSize + 1.0f);
                    }
                }
                player->soundEffects &= ~HOLD_BANANA_SOUND_EFFECT;
                temp_s0->timer = 0;
                temp_s0->branch = CPU_STRATEGY_WAIT_NEXT_ITEM;
                break;

            case CPU_STRATEGY_THROW_BANANA:
                temp_s0->actorIndex = use_banana_item(player);
                if ((temp_s0->actorIndex >= 0) && (temp_s0->actorIndex < 0x64)) {
                    banana = (struct BananaActor*) GET_ACTOR(temp_s0->actorIndex);
                    banana->state = BANANA_ON_GROUND;
                    player->soundEffects |= HOLD_BANANA_SOUND_EFFECT;
                    temp_s0->branch = CPU_STRATEGY_HOLD_THROW_BANANA;
                    temp_s0->timer = 0;
                    temp_s0->numItemUse += 1;
                    pathPoint = &gTrackPaths[gPathIndexByPlayerId[0]]
                                            [(gNearestPathPointByPlayerId[gBestRankedHumanPlayer] + 0x1E) %
                                             gPathCountByPathIndex[gPathIndexByPlayerId[gBestRankedHumanPlayer]]];
                    banana->velocity[0] = (pathPoint->posX - player->pos[0]) / 20.0;
                    banana->velocity[1] = ((pathPoint->posY - player->pos[1]) / 20.0) + 4.0;
                    banana->velocity[2] = (pathPoint->posZ - player->pos[2]) / 20.0;
                    banana->pos[1] = player->pos[1];
                    func_800C92CC(playerId, SOUND_ARG_LOAD(0x29, 0x00, 0x80, 0x09));
                    func_800C98B8(player->pos, player->velocity, SOUND_ARG_LOAD(0x19, 0x01, 0x80, 0x14));
                } else {
                    temp_s0->branch = CPU_STRATEGY_WAIT_NEXT_ITEM;
                    temp_s0->timer = 0;
                }
                break;

            case CPU_STRATEGY_HOLD_THROW_BANANA:
                banana = (struct BananaActor*) GET_ACTOR(temp_s0->actorIndex);
                if ((((!(banana->flags & 0x8000)) || (banana->type != ACTOR_BANANA)) ||
                     (banana->state != BANANA_ON_GROUND)) ||
                    (playerId != banana->playerId)) {
                    temp_s0->branch = CPU_STRATEGY_WAIT_NEXT_ITEM;
                    temp_s0->timer = 0;
                    player->soundEffects &= ~HOLD_BANANA_SOUND_EFFECT;
                } else {
                    banana->velocity[1] -= 0.4;
                    banana->pos[0] += banana->velocity[0];
                    banana->pos[1] += banana->velocity[1];
                    banana->pos[2] += banana->velocity[2];
                    if (temp_s0->timer >= 0x15) {
                        temp_s0->branch = CPU_STRATEGY_END_THROW_BANANA;
                    }
                }
                break;

            case CPU_STRATEGY_END_THROW_BANANA:
                banana = (struct BananaActor*) GET_ACTOR(temp_s0->actorIndex);
                if ((((!(banana->flags & 0x8000)) || (banana->type != ACTOR_BANANA)) ||
                     (banana->state != BANANA_ON_GROUND)) ||
                    (playerId != banana->playerId)) {
                    if (playerId != banana->playerId) {}
                } else {
                    banana->state = DROPPED_BANANA;
                    banana->velocity[0] = 0.0f;
                    banana->velocity[1] = 0.0f;
                    banana->velocity[2] = 0.0f;
                    banana->pos[1] =
                        spawn_actor_on_surface(banana->pos[0], (f32) (((f64) banana->pos[1]) + 30.0), banana->pos[2]) +
                        (banana->boundingBoxSize + 1.0f);
                }
                player->soundEffects &= ~HOLD_BANANA_SOUND_EFFECT;
                temp_s0->branch = CPU_STRATEGY_WAIT_NEXT_ITEM;
                temp_s0->timer = 0;
                break;

            case CPU_STRATEGY_ITEM_GREEN_SHELL:
                if (((s32) gNumActors) < 0x50) {
                    temp_s0->actorIndex = use_green_shell_item(player);
                    if ((temp_s0->actorIndex >= 0) && (temp_s0->actorIndex < 0x64)) {
                        temp_s0->branch = CPU_STRATEGY_HOLD_GREEN_SHELL;
                        temp_s0->timer = 0;
                        temp_s0->numItemUse += 1;
                        temp_s0->timeBeforeThrow = (random_int(3U) * 0x14) + 0xA;
                    } else {
                        temp_s0->branch = CPU_STRATEGY_WAIT_NEXT_ITEM;
                    }
                } else {
                    temp_s0->branch = CPU_STRATEGY_WAIT_NEXT_ITEM;
                }
                break;

            case CPU_STRATEGY_HOLD_GREEN_SHELL:
                actor = GET_ACTOR(temp_s0->actorIndex);
                if ((((!(actor->flags & 0x8000)) || (actor->type != ACTOR_GREEN_SHELL)) ||
                     (actor->state != HELD_SHELL)) ||
                    (playerId != actor->rot[2])) {
                    temp_s0->branch = CPU_STRATEGY_WAIT_NEXT_ITEM;
                    temp_s0->timer = 0;
                } else if (temp_s0->timeBeforeThrow < temp_s0->timer) {
                    temp_s0->branch = CPU_STRATEGY_THROW_GREEN_SHELL;
                    temp_s0->timer = 0;
                }
                break;

            case CPU_STRATEGY_THROW_GREEN_SHELL:
                actor = GET_ACTOR(temp_s0->actorIndex);
                if ((((!(actor->flags & 0x8000)) || (actor->type != ACTOR_GREEN_SHELL)) ||
                     (actor->state != HELD_SHELL)) ||
                    (playerId != actor->rot[2])) {
                    temp_s0->branch = CPU_STRATEGY_WAIT_NEXT_ITEM;
                    temp_s0->timer = 0;
                } else {
                    actor->state = RELEASED_SHELL;
                    temp_s0->timer = 0;
                    temp_s0->branch = CPU_STRATEGY_WAIT_NEXT_ITEM;
                }
                break;

            case CPU_STRATEGY_ITEM_RED_SHELL:
                if (((s32) gNumActors) < 0x50) {
                    temp_s0->actorIndex = use_red_shell_item(player);
                    if ((temp_s0->actorIndex >= 0) && (temp_s0->actorIndex < 0x64)) {
                        temp_s0->branch = CPU_STRATEGY_HOLD_RED_SHELL;
                        temp_s0->timer = 0;
                        temp_s0->numItemUse += 1;
                        temp_s0->timeBeforeThrow = (random_int(3U) * 0x14) + 0xA;
                    } else {
                        temp_s0->branch = CPU_STRATEGY_WAIT_NEXT_ITEM;
                    }
                } else {
                    temp_s0->branch = CPU_STRATEGY_WAIT_NEXT_ITEM;
                }
                break;

            case CPU_STRATEGY_HOLD_RED_SHELL:
                shell = (struct ShellActor*) GET_ACTOR(temp_s0->actorIndex);
                if ((((!(shell->flags & 0x8000)) || (shell->type != ACTOR_RED_SHELL)) ||
                     (shell->state != HELD_SHELL)) ||
                    (playerId != shell->playerId)) {
                    temp_s0->branch = CPU_STRATEGY_WAIT_NEXT_ITEM;
                    temp_s0->timer = 0;
                } else if (temp_s0->timeBeforeThrow < temp_s0->timer) {
                    temp_s0->branch = CPU_STRATEGY_THROW_RED_SHELL;
                }
                break;

            case CPU_STRATEGY_THROW_RED_SHELL:
                clear_expired_strategies(temp_s0);
                shell = (struct ShellActor*) GET_ACTOR(temp_s0->actorIndex);
                if ((((!(shell->flags & 0x8000)) || (shell->type != ACTOR_RED_SHELL)) ||
                     (shell->state != HELD_SHELL)) ||
                    (playerId != shell->playerId)) {
                    temp_s0->branch = CPU_STRATEGY_WAIT_NEXT_ITEM;
                    temp_s0->timer = 0;
                } else {
                    shell->state = RELEASED_SHELL;
                    temp_s0->timer = 0;
                    temp_s0->branch = CPU_STRATEGY_WAIT_NEXT_ITEM;
                }
                break;

            case CPU_STRATEGY_ITEM_BANANA_BUNCH:
                if (((s32) gNumActors) < 0x50) {
                    temp_s0->actorIndex = use_banana_bunch_item(player);
                    if ((temp_s0->actorIndex >= 0) && (temp_s0->actorIndex < 0x64)) {
                        temp_s0->branch = CPU_STRATEGY_WAIT_INIT_BANANA_BUNCH;
                        temp_s0->timer = 0;
                        temp_s0->numItemUse += 1;
                        temp_s0->timeBeforeThrow = (random_int(3U) * 0x14) + 0x3C;
                    } else {
                        temp_s0->branch = CPU_STRATEGY_WAIT_NEXT_ITEM;
                    }
                } else {
                    temp_s0->branch = CPU_STRATEGY_WAIT_NEXT_ITEM;
                }
                break;

            case CPU_STRATEGY_WAIT_INIT_BANANA_BUNCH:
                bananaBunchParent = (struct BananaBunchParent*) GET_ACTOR(temp_s0->actorIndex);
                if (bananaBunchParent->state == 6) {
                    isNoProblem = false;
                    if (bananaBunchParent->bananaIndices[4] != (-1)) {
                        isNoProblem = true;
                    }
                    if (bananaBunchParent->bananaIndices[3] != (-1)) {
                        isNoProblem = true;
                    }
                    if (bananaBunchParent->bananaIndices[2] != (-1)) {
                        isNoProblem = true;
                    }
                    if (bananaBunchParent->bananaIndices[1] != (-1)) {
                        isNoProblem = true;
                    }
                    if (bananaBunchParent->bananaIndices[0] != (-1)) {
                        isNoProblem = true;
                    }
                    if ((bananaBunchParent->type != ACTOR_BANANA_BUNCH) || (isNoProblem == false)) {
                        temp_s0->branch = CPU_STRATEGY_WAIT_NEXT_ITEM;
                        temp_s0->timer = 0;
                    } else if (temp_s0->timeBeforeThrow < temp_s0->timer) {
                        temp_s0->branch = CPU_STRATEGY_DROP_BANANA_BUNCH;
                        temp_s0->numDroppedBananaBunch = 0;
                        temp_s0->timer = 0;
                    }
                }
                break;

            case CPU_STRATEGY_DROP_BANANA_BUNCH:
                if ((((s16) temp_s0->timer) % 10) == 0) {
                    if (temp_s0->numDroppedBananaBunch < 5) {
                        bananaBunchParent = (struct BananaBunchParent*) GET_ACTOR(temp_s0->actorIndex);
                        isNoProblem = 0;
                        switch (temp_s0->numDroppedBananaBunch) {
                            case 0:
                                if (bananaBunchParent->bananaIndices[4] != (-1)) {
                                    isNoProblem = true;
                                }
                                break;

                            case 1:
                                if (bananaBunchParent->bananaIndices[3] != (-1)) {
                                    isNoProblem = true;
                                }
                                break;

                            case 2:
                                if (bananaBunchParent->bananaIndices[2] != (-1)) {
                                    isNoProblem = true;
                                }
                                break;

                            case 3:
                                if (bananaBunchParent->bananaIndices[1] != (-1)) {
                                    isNoProblem = true;
                                }
                                break;

                            case 4:
                                if (bananaBunchParent->bananaIndices[0] != (-1)) {
                                    isNoProblem = true;
                                }
                                break;
                        }

                        if (((bananaBunchParent->type == ACTOR_BANANA_BUNCH) && (bananaBunchParent->state == 6)) &&
                            (isNoProblem == true)) {
                            drop_banana_in_banana_bunch(bananaBunchParent);
                        }
                        temp_s0->numDroppedBananaBunch += 1;
                    } else {
                        temp_s0->branch = CPU_STRATEGY_WAIT_NEXT_ITEM;
                        temp_s0->timer = 0;
                    }
                }
                break;

            case CPU_STRATEGY_ITEM_FAKE_ITEM_BOX:
                temp_s0->actorIndex = use_fake_itembox_item(player);
                if ((temp_s0->actorIndex >= 0) && (temp_s0->actorIndex < 0x64)) {
                    temp_s0->branch = CPU_STRATEGY_HOLD_FAKE_ITEM_BOX;
                    temp_s0->timer = 0;
                    temp_s0->numItemUse += 1;
                    temp_s0->timeBeforeThrow = (random_int(3U) * 0x14) + 0xA;
                } else {
                    temp_s0->branch = CPU_STRATEGY_WAIT_NEXT_ITEM;
                }
                break;

            case CPU_STRATEGY_HOLD_FAKE_ITEM_BOX:
                fakeItemBox = (struct FakeItemBox*) GET_ACTOR(temp_s0->actorIndex);
                if ((((!(fakeItemBox->flags & 0x8000)) || (fakeItemBox->type != ACTOR_FAKE_ITEM_BOX)) ||
                     (fakeItemBox->state != 0)) ||
                    (playerId != ((s32) fakeItemBox->playerId))) {
                    temp_s0->branch = CPU_STRATEGY_WAIT_NEXT_ITEM;
                    temp_s0->timer = 0;
                } else if (temp_s0->timeBeforeThrow < temp_s0->timer) {
                    temp_s0->branch = CPU_STRATEGY_THROW_FAKE_ITEM_BOX;
                }
                break;

            case CPU_STRATEGY_THROW_FAKE_ITEM_BOX:
                fakeItemBox = (struct FakeItemBox*) GET_ACTOR(temp_s0->actorIndex);
                if ((((!(fakeItemBox->flags & 0x8000)) || (fakeItemBox->type != ACTOR_FAKE_ITEM_BOX)) ||
                     (fakeItemBox->state != 0)) ||
                    (playerId != ((s32) fakeItemBox->playerId))) {
                    if (playerId != fakeItemBox->rot[0]) {}
                } else {
                    func_802A1064(fakeItemBox);
                    if (D_801631E0[playerId] == true) {
                        fakeItemBox->pos[1] = spawn_actor_on_surface(fakeItemBox->pos[0], fakeItemBox->pos[1] + 30.0,
                                                                     fakeItemBox->pos[2]) +
                                              fakeItemBox->boundingBoxSize;
                    }
                }
                temp_s0->branch = CPU_STRATEGY_WAIT_NEXT_ITEM;
                temp_s0->timer = 0;
                break;

            case CPU_STRATEGY_ITEM_THUNDERBOLT:
                use_thunder_item(player);
                func_800CAC60(playerId);
                func_8009E5BC();
                temp_s0->branch = CPU_STRATEGY_END_THUNDERBOLT;
                temp_s0->timer = 0;
                temp_s0->numItemUse += 1;
                break;

            case CPU_STRATEGY_END_THUNDERBOLT:
                if (temp_s0->timer >= 0xF1) {
                    func_800CAD40((s32) ((u8) playerId));
                    temp_s0->branch = CPU_STRATEGY_WAIT_NEXT_ITEM;
                    temp_s0->timer = 0;
                }
                break;

            case CPU_STRATEGY_ITEM_STAR:
                player->soundEffects |= STAR_SOUND_EFFECT;
                temp_s0->branch = CPU_STRATEGY_END_ITEM_STAR;
                temp_s0->timer = 0;
                temp_s0->numItemUse += 1;
                break;

            case CPU_STRATEGY_END_ITEM_STAR:
                if (!(player->effects & STAR_EFFECT)) {
                    temp_s0->branch = CPU_STRATEGY_WAIT_NEXT_ITEM;
                }
                temp_s0->timer = 0;
                break;

            case CPU_STRATEGY_ITEM_BOO:
                player->soundEffects |= BOO_SOUND_EFFECT;
                temp_s0->branch = CPU_STRATEGY_WAIT_END_BOO;
                temp_s0->timer = 0;
                temp_s0->numItemUse += 1;
                break;

            case CPU_STRATEGY_WAIT_END_BOO:
                if (!(player->effects & BOO_EFFECT)) {
                    temp_s0->branch = CPU_STRATEGY_WAIT_NEXT_ITEM;
                }
                temp_s0->timer = 0;
                break;

            case CPU_STRATEGY_ITEM_MUSHROOM:
                player->soundEffects |= BOOST_SOUND_EFFECT;
                temp_s0->branch = CPU_STRATEGY_WAIT_NEXT_ITEM;
                temp_s0->timer = 0;
                temp_s0->numItemUse += 1;
                break;

            case CPU_STRATEGY_ITEM_DOUBLE_MUSHROOM:
                if (temp_s0->timer >= 0x3D) {
                    player->soundEffects |= BOOST_SOUND_EFFECT;
                    temp_s0->branch = CPU_STRATEGY_ITEM_MUSHROOM;
                    temp_s0->timer = 0;
                }
                break;

            case CPU_STRATEGY_ITEM_TRIPLE_MUSHROOM:
                if (temp_s0->timer >= 0x3D) {
                    player->soundEffects |= BOOST_SOUND_EFFECT;
                    temp_s0->branch = CPU_STRATEGY_ITEM_DOUBLE_MUSHROOM;
                    temp_s0->timer = 0;
                }
                break;

            case CPU_STRATEGY_ITEM_SUPER_MUSHROOM:
                temp_s0->branch = CPU_STRATEGY_USE_SUPER_MUSHROOM;
                temp_s0->timer = 0;
                temp_s0->timeBeforeThrow = 0x0258;
                break;

            case CPU_STRATEGY_USE_SUPER_MUSHROOM:
                if ((((s16) temp_s0->timer) % 60) == 0) {
                    player->soundEffects |= BOOST_SOUND_EFFECT;
                    if (temp_s0->timeBeforeThrow < temp_s0->timer) {
                        temp_s0->branch = CPU_STRATEGY_WAIT_NEXT_ITEM;
                        temp_s0->timer = 0;
                    }
                }
                break;
            case CPU_STRATEGY_ITEM_BLUE_SPINY_SHELL:
                if (((s32) gNumActors) < 0x50) {
                    temp_s0->actorIndex = use_blue_shell_item(player);
                    if ((temp_s0->actorIndex >= 0) && (temp_s0->actorIndex < 0x64)) {
                        temp_s0->branch = CPU_STRATEGY_HOLD_BLUE_SPINY_SHELL;
                        temp_s0->timer = 0;
                        temp_s0->numItemUse += 1;
                        temp_s0->timeBeforeThrow = (random_int(3U) * 0x14) + 0xA;
                    } else {
                        temp_s0->branch = CPU_STRATEGY_WAIT_NEXT_ITEM;
                    }
                } else {
                    temp_s0->branch = CPU_STRATEGY_WAIT_NEXT_ITEM;
                }
                break;
            case CPU_STRATEGY_HOLD_BLUE_SPINY_SHELL:
                shell = (struct ShellActor*) GET_ACTOR(temp_s0->actorIndex);
                if ((((!(shell->flags & 0x8000)) || (shell->type != ACTOR_BLUE_SPINY_SHELL)) ||
                     (shell->state != HELD_SHELL)) ||
                    (playerId != shell->playerId)) {
                    temp_s0->branch = CPU_STRATEGY_WAIT_NEXT_ITEM;
                    temp_s0->timer = 0;
                } else if (temp_s0->timeBeforeThrow < temp_s0->timer) {
                    temp_s0->branch = CPU_STRATEGY_THROW_BLUE_SPINY_SHELL;
                }
                break;
            case CPU_STRATEGY_THROW_BLUE_SPINY_SHELL:
                clear_expired_strategies(temp_s0);
                shell = (struct ShellActor*) GET_ACTOR(temp_s0->actorIndex);
                if ((((!(shell->flags & 0x8000)) || (shell->type != ACTOR_BLUE_SPINY_SHELL)) ||
                     (shell->state != HELD_SHELL)) ||
                    (playerId != shell->playerId)) {
                    temp_s0->branch = CPU_STRATEGY_WAIT_NEXT_ITEM;
                    temp_s0->timer = 0;
                } else {
                    shell->state = RELEASED_SHELL;
                    temp_s0->timer = 0;
                    temp_s0->branch = CPU_STRATEGY_WAIT_NEXT_ITEM;
                }
                break;

            default:
                break;
        }

        if (temp_s0->timer < 0x2710) {
            temp_s0->timer += 1;
        }
        if (player->effects & (BOO_EFFECT | BOOST_EFFECT | STAR_EFFECT)) { // 0x80002200
            temp_s0->timer = 0;
        }
    }
}
#else
GLOBAL_ASM("asm/non_matchings/code_80005FD0/cpu_use_item_strategy.s")
#endif

void func_8001BE78(void) {
    Player* temp_s1;
    TrackPathPoint* temp_s0;
    s32 i;

    init_players();
    for (i = 0; i < 4; i++) {
        temp_s1 = &gPlayerOne[i];
        temp_s1->type &= 0xDFFF;
        gPathIndexByPlayerId[i] = i;
        gPlayerTrackPositionFactorInstruction[i].unkC = 0.0f;
        gPlayerTrackPositionFactorInstruction[i].target = 0.0f;
        gPlayerTrackPositionFactorInstruction[i].current = 0.0f;
        switch (i) {
            case 0:
                gNearestPathPointByPlayerId[i] = 6;
                break;
            case 1:
                gNearestPathPointByPlayerId[i] = 1;
                break;
            case 2:
                gNearestPathPointByPlayerId[i] = 6;
                break;
            case 3:
                gNearestPathPointByPlayerId[i] = 1;
                break;
        }
        temp_s0 = &gTrackPaths[i][gNearestPathPointByPlayerId[i]];
        temp_s1->pos[0] = (f32) temp_s0->posX;
        temp_s1->pos[1] =
            spawn_actor_on_surface((f32) temp_s0->posX, 2000.0f, (f32) temp_s0->posZ) + temp_s1->boundingBoxSize;
        temp_s1->pos[2] = (f32) temp_s0->posZ;
        temp_s1->rotation[1] = (s16) *gPathExpectedRotation[i];
        apply_cpu_turn(temp_s1, 0);
        temp_s1++;
        D_80163410[i] = 0;
    }
}

void func_8001C05C(void) {
    init_segment_racing();
    gCurrentCourseId = COURSE_AWARD_CEREMONY;
    SelectPodiumCeremony();
    D_8016347C = 0;
    D_8016347E = 0;
    D_80163480 = 0;
    D_80163484 = 0;
    init_course_path_point();
    func_80014DE4(0);
    func_8001BE78();
    D_80163418[0] = -3202.475097656f;
    D_80163428[0] = 19.166999817f;
    D_80163438[0] = -477.623992920f;
    D_80163418[1] = -3205.080078125f;
    D_80163428[1] = 19.166999817f;
    D_80163438[1] = -462.851989746f;
    D_80163418[2] = -3199.870117188f;
    D_80163428[2] = 19.166999817f;
    D_80163438[2] = -492.395996094f;
    D_80163418[3] = -2409.197021484f;
    D_80163428[3] = 0.0f;
    D_80163438[3] = -355.253997803;
}

void func_8001C14C(void) {
    f32 temp_f0;
    f32 temp_f2;
    s32 var_s1;
    Player* player;

    if (D_8016347C == 1) {
        D_80163480 += 1;
    }
    if ((D_8016347E == 1) && (gBombKarts[0].state == 0) && (D_802874D8.unk1D >= 3)) {
        D_80163484++;
        if (D_80163484 >= 0xF) {
            D_80163484 = 0;
            D_8016347E = 2;
            func_8009265C();
        }
    }
    for (var_s1 = 0; var_s1 < 4; var_s1++) {
        if ((var_s1 == 3) && (D_8016347C == 0)) {
            break;
        }

        player = &gPlayerOne[var_s1];
        update_player(var_s1);
        if (!(player->type & 0x2000)) {
            temp_f0 = D_80163418[var_s1] - player->pos[0];
            temp_f2 = D_80163438[var_s1] - player->pos[2];
            if ((f64) ((temp_f0 * temp_f0) + (temp_f2 * temp_f2)) < 1.0) {
                if (var_s1 != 3) {
                    if (1) {}
                    // Why oh why is a ternary required here? Who does that?
                    (D_8016347C == 0) ? (player->type |= 0x2000) : (player->type &= ~0x2000);
                    if ((gPlayerOne->type & 0x2000) && (gPlayerTwo->type & 0x2000) && (gPlayerThree->type & 0x2000)) {
                        D_8016347C = 1;
                        D_80163480 = 0;
                    }
                } else if (D_8016347E == 0) {
                    if (!(player->effects & 0x01000000)) {
                        player->type |= 0x2000;
                    }
                    D_8016347E = 1;
                    D_80163484 = 0;
                } else if (!(player->effects & 0x01000000)) {
                    player->type |= 0x2000;
                }
            }
        }
    }
}

void render_bomb_karts_wrap(s32 cameraId) {
    if (IsPodiumCeremony()) {
        if (gBombKarts[0].waypointIndex >= 16) {
            render_bomb_karts(PLAYER_FOUR);
        }
    } else {
        if (gModeSelection == VERSUS) {
            render_bomb_karts(cameraId);
        }
    }
}

UNUSED void func_8001C42C(void) {
    if (D_800DDB20 == 0) {
        if ((gControllerThree->buttonPressed & 0x20) != 0) {
            D_800DDB20 = 1;
        }
    } else {
        if ((gControllerThree->buttonPressed & 0x20) != 0) {
            D_800DDB20 = 0;
        }
        func_80057C60();
        gSPDisplayList(gDisplayListHead++, D_0D0076F8);
        func_80057CE4();
    }
}
