#include #include #include "Train.h" #include #include "engine/tracks/Track.h" #include "engine/vehicles/Utils.h" #include "engine/World.h" #include "port/Game.h" extern "C" { #include "macros.h" #include "main.h" #include "defines.h" #include "code_80005FD0.h" #include "vehicles.h" #include "racing/actors.h" #include "racing/math_util.h" #include "sounds.h" #include "update_objects.h" #include "waypoints.h" #include "code_80057C60.h" #include "math_util_2.h" #include "render_objects.h" #include "assets/models/common_data.h" // #include "common_structs.h" } // The two counts are so we can spawn trains at specific points or use auto distribution size_t ATrain::_count = 0; // pathIndex, array of spawn points std::map> ATrain::TrainCounts; ATrain::ATrain(const SpawnParams& params) : AActor(params) { Name = "Train"; ResourceName = "mk:train"; BoundingBoxSize = 2.0f; TrainCarStuff* ptr1; TrackPathPoint* pos; Index = _count; PassengerCarsCount = params.Count.value_or(0); bool tender = params.Bool.value_or(true); // The path to spawn the train at uint32_t pathIndex = params.PathIndex.value_or(0); // The point along the path to spawn the train at uint32_t pathPoint = 0; SpawnType = static_cast(params.Type.value_or(SpawnMode::POINT)); switch(SpawnType) { case SpawnMode::POINT: // Spawn train at a specific path point pathPoint = params.PathPoint.value_or(0); TrainCounts[pathIndex].push_back(pathPoint); break; case SpawnMode::AUTO: // Automatically distribute trains based on a specific path point printf("vehicle path size %zu\n", gVehiclePathSize); pathPoint = GetVehiclePathPointDistributed(TrainCounts[pathIndex], gVehiclePathSize); TrainCounts[pathIndex].push_back(pathPoint); printf("train spawn path point: %d\n", pathPoint); break; } // Set to the default value std::fill(SmokeParticles, SmokeParticles + 128, NULL_OBJECT_ID); for (size_t i = 0; i < PassengerCarsCount; i++) { PassengerCars.push_back(TrainCarStuff()); } // 120.0f is about the maximum usable value for (size_t i = 0; i < PassengerCars.size(); i++) { pathPoint += 4; ptr1 = &PassengerCars[i]; pos = &gVehicle2DPathPoint[pathPoint]; set_vehicle_pos_path_point(ptr1, pos, pathPoint); } // Smaller offset for the tender pathPoint += 3; pos = &gVehicle2DPathPoint[pathPoint]; set_vehicle_pos_path_point(&this->Tender, pos, pathPoint); pathPoint += 4; pos = &gVehicle2DPathPoint[pathPoint]; set_vehicle_pos_path_point(&Locomotive, pos, pathPoint); // Only use locomotive unless overwritten below. // Fall back in-case someone tries to spawn a train with carriages but no tender; not allowed. if (PassengerCarsCount > 0) { tender = HAS_TENDER; } Tender.isActive = static_cast(tender); for (size_t i = 0; i < PassengerCarsCount; i++) { PassengerCars[i].isActive = 1; } AnotherSmokeTimer = 0; TrainCarStuff* tempLocomotive; TrainCarStuff* tempTender; TrainCarStuff* tempPassengerCar; Vec3s trainCarRot; s16 trainCarYRot; f32 origXPos; f32 origZPos; tempLocomotive = &Locomotive; origXPos = tempLocomotive->position[0]; origZPos = tempLocomotive->position[2]; trainCarYRot = update_vehicle_following_path(tempLocomotive->position, (s16*) &tempLocomotive->waypointIndex, Speed); tempLocomotive->velocity[0] = tempLocomotive->position[0] - origXPos; tempLocomotive->velocity[2] = tempLocomotive->position[2] - origZPos; vec3s_set(trainCarRot, 0, trainCarYRot, 0); tempLocomotive->actorIndex = add_actor_to_empty_slot(tempLocomotive->position, trainCarRot, tempLocomotive->velocity, ACTOR_TRAIN_ENGINE); tempTender = &Tender; if (tempTender->isActive == 1) { origXPos = tempTender->position[0]; origZPos = tempTender->position[2]; trainCarYRot = update_vehicle_following_path(tempTender->position, (s16*) &tempTender->waypointIndex, Speed); tempTender->velocity[0] = tempTender->position[0] - origXPos; tempTender->velocity[2] = tempTender->position[2] - origZPos; vec3s_set(trainCarRot, 0, trainCarYRot, 0); tempTender->actorIndex = add_actor_to_empty_slot(tempTender->position, trainCarRot, tempTender->velocity, ACTOR_TRAIN_TENDER); } for (size_t i = 0; i < PassengerCars.size(); i++) { tempPassengerCar = &PassengerCars[i]; if (tempPassengerCar->isActive == 1) { origXPos = tempPassengerCar->position[0]; origZPos = tempPassengerCar->position[2]; trainCarYRot = update_vehicle_following_path(tempPassengerCar->position, (s16*) &tempPassengerCar->waypointIndex, Speed); tempPassengerCar->velocity[0] = tempPassengerCar->position[0] - origXPos; tempPassengerCar->velocity[2] = tempPassengerCar->position[2] - origZPos; vec3s_set(trainCarRot, 0, trainCarYRot, 0); tempPassengerCar->actorIndex = add_actor_to_empty_slot( tempPassengerCar->position, trainCarRot, tempPassengerCar->velocity, ACTOR_TRAIN_PASSENGER_CAR); } } _count++; } void ATrain::SetSpawnParams(SpawnParams& params) { AActor::SetSpawnParams(params); params.Name = "mk:train"; params.Type = static_cast(SpawnType); params.Bool = Tender.isActive; params.Speed = Speed; params.Count = PassengerCarsCount; params.PathIndex = PathIndex; params.PathPoint = PathPoint; } bool ATrain::IsMod() { return true; } void ATrain::SyncComponents(TrainCarStuff* trainCar, s16 orientationY) { struct TrainCar* trainCarActor; //! @todo: Change actorIndex to ptr to TrainCar actor 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[1] = trainCar->velocity[1]; trainCarActor->velocity[2] = trainCar->velocity[2]; } void ATrain::Tick() { TrainCarStuff* car; u16 oldWaypointIndex; s16 orientationYUpdate; s32 j; Vec3f smokePos; FVector temp_f20 = { Locomotive.position[0], Locomotive.position[1], Locomotive.position[2] }; AnotherSmokeTimer += 1; oldWaypointIndex = (u16) Locomotive.waypointIndex; orientationYUpdate = update_vehicle_following_path(Locomotive.position, (s16*) &Locomotive.waypointIndex, Speed); Locomotive.velocity[0] = Locomotive.position[0] - temp_f20.x; Locomotive.velocity[1] = Locomotive.position[1] - temp_f20.y; Locomotive.velocity[2] = Locomotive.position[2] - temp_f20.z; sync_train_components(&Locomotive, orientationYUpdate); if ((oldWaypointIndex != Locomotive.waypointIndex) && ((Locomotive.waypointIndex == 0x00BE) || (Locomotive.waypointIndex == 0x0140))) { // play crossing bell sound func_800C98B8(Locomotive.position, Locomotive.velocity, SOUND_ARG_LOAD(0x19, 0x01, 0x80, 0x0E)); } else if (random_int(100) == 0) { // play train whistle sound func_800C98B8(Locomotive.position, Locomotive.velocity, SOUND_ARG_LOAD(0x19, 0x01, 0x80, 0x0D)); } SomeFlags = set_vehicle_render_distance_flags(Locomotive.position, TRAIN_SMOKE_RENDER_DISTANCE, SomeFlags); // Renders locomotive smoke on all screens if any player is within range. if ((((s16) AnotherSmokeTimer % 5) == 0) && (SomeFlags != 0)) { smokePos[0] = Locomotive.position[0]; smokePos[1] = (f32) ((f64) Locomotive.position[1] + 65.0); smokePos[2] = (f32) ((f64) Locomotive.position[2] + 25.0); adjust_position_by_angle(smokePos, Locomotive.position, orientationYUpdate); // spawn_train_smoke(Index, smokePos, 1.1f); AddSmoke(Index, smokePos, 1.1f); } car = &Tender; if (car->isActive == 1) { temp_f20.x = car->position[0]; temp_f20.y = car->position[1]; temp_f20.z = car->position[2]; orientationYUpdate = update_vehicle_following_path(car->position, (s16*) &car->waypointIndex, Speed); car->velocity[0] = car->position[0] - temp_f20.x; car->velocity[1] = car->position[1] - temp_f20.y; car->velocity[2] = car->position[2] - temp_f20.z; sync_train_components(car, orientationYUpdate); } for (j = 0; j < PassengerCars.size(); j++) { car = &PassengerCars[j]; if (car->isActive == 1) { temp_f20.x = car->position[0]; temp_f20.y = car->position[1]; temp_f20.z = car->position[2]; orientationYUpdate = update_vehicle_following_path(car->position, (s16*) &car->waypointIndex, Speed); car->velocity[0] = car->position[0] - temp_f20.x; car->velocity[1] = car->position[1] - temp_f20.y; car->velocity[2] = car->position[2] - temp_f20.z; sync_train_components(car, orientationYUpdate); } } } void ATrain::VehicleCollision(s32 playerId, Player* player) { TrainCarStuff* trainCar; f32 playerPosX; f32 playerPosZ; f32 x_dist; f32 z_dist; s32 trainIndex; if (D_801631E0[playerId] != 1) { if (!(player->effects & 0x01000000)) { playerPosX = player->pos[0]; playerPosZ = player->pos[2]; trainCar = &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->triggers |= VERTICAL_TUMBLE_TRIGGER; } trainCar = &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->triggers |= VERTICAL_TUMBLE_TRIGGER; } } } } for (size_t i = 0; i < PassengerCars.size(); i++) { trainCar = &PassengerCars[i]; 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->triggers |= VERTICAL_TUMBLE_TRIGGER; } } } } } } } } void ATrain::Draw(Camera* camera) { } s32 ATrain::AddSmoke(s32 trainIndex, Vec3f pos, f32 velocity) { s32 objectIndex = add_unused_obj_index(SmokeParticles, &NextParticlePtr, gObjectParticle2_SIZE); if (objectIndex != NULL_OBJECT_ID) { init_train_smoke(objectIndex, pos, velocity); } return objectIndex; } void ATrain::DrawEditorProperties() { ImGui::Text("Passenger Cars"); ImGui::SameLine(); int count = static_cast(PassengerCarsCount); if (ImGui::InputInt("##Count", &count)) { // Clamp to uint32_t range (only lower bound needed if assuming positive values) if (count < 0) count = 0; PassengerCarsCount = static_cast(count); } ImGui::SameLine(); if (ImGui::Button(ICON_FA_UNDO "##ResetCount")) { PassengerCarsCount = 0; } ImGui::Text("Spawn Mode"); ImGui::SameLine(); int32_t type = static_cast(SpawnType); const char* items[] = { "POINT", "AUTO" }; if (ImGui::Combo("##Type", &type, items, IM_ARRAYSIZE(items))) { SpawnType = static_cast(type); } if (type == ATrain::SpawnMode::POINT) { ImGui::Text("Path Index"); ImGui::SameLine(); int pathIndex = static_cast(PathIndex); if (ImGui::InputInt("##PathIndex", &pathIndex)) { if (pathIndex < 0) pathIndex = 0; PathIndex = static_cast(pathIndex); } ImGui::SameLine(); if (ImGui::Button(ICON_FA_UNDO "##ResetPathIndex")) { PathIndex = 0; } ImGui::Text("Path Point"); ImGui::SameLine(); int pathPoint = static_cast(PathPoint); if (ImGui::InputInt("##PathPoint", &pathPoint)) { if (pathPoint < 0) pathPoint = 0; PathPoint = static_cast(pathPoint); } ImGui::SameLine(); if (ImGui::Button(ICON_FA_UNDO "##ResetPathPoint")) { PathPoint = 0; } } ImGui::Text("Has Tender"); ImGui::SameLine(); bool theBool = HasTender; if (ImGui::Checkbox("##Bool", &theBool)) { HasTender = static_cast(theBool); } ImGui::SameLine(); if (ImGui::Button(ICON_FA_UNDO "##ResetBool")) { HasTender = TenderStatus::NO_TENDER; } ImGui::Text("Speed"); ImGui::SameLine(); float speed = Speed; if (ImGui::DragFloat("##Speed", &speed, 0.1f)) { Speed = speed; } ImGui::SameLine(); if (ImGui::Button(ICON_FA_UNDO "##ResetSpeed")) { Speed = 0.0f; } }