diff --git a/src/gtasa/effects/custom/vehicle/OilOnRoadsEffect.cpp b/src/gtasa/effects/custom/vehicle/OilOnRoadsEffect.cpp new file mode 100644 index 00000000..8a01c910 --- /dev/null +++ b/src/gtasa/effects/custom/vehicle/OilOnRoadsEffect.cpp @@ -0,0 +1,289 @@ +#include "util/EffectBase.h" +#include "util/GameUtil.h" +#include "util/GenericUtil.h" + +#include +#include + +#include +#include + +using namespace plugin; +namespace +{ +constexpr float AFFECTED_RADIUS = 128.0f; +constexpr int MAX_NODES_COUNT = 1024; +CNodeAddress resultNodes[MAX_NODES_COUNT]; +float gpsDistance = 0.0f; +short nodesCount = 0; + +bool +pointInRadius (const CVector &p1, const CVector ¢er, const float R) +{ + auto dx = std::abs (p1.x - center.x); + auto dy = std::abs (p1.y - center.y); + auto dz = std::abs (p1.z - center.z); + if ((dx + dy + dz) <= R) return true; + + return ((dx * dx + dy * dy + dz * dz) <= R * R); +} + +} // namespace + +enum class OilSpotState +{ + APPEAR, + IDLE, + DISAPPEAR, + DEAD +}; + +struct OilSpot +{ + const float SPOT_SIZE = 5.0f; + const float APPEAR_TIME = 750.0f; + const float DISAPPEAR_TIME = 750.0f; + const float MAX_LIFETIME = 10000.0f; + + CVehicle *lastVehicle = nullptr; + OilSpotState state = OilSpotState::DEAD; + CompressedVector compressedPos{}; + CVector pos{}; + float radius = 0.0f; + float lifetime = 0.0f; + float angle = 0.0f; + + OilSpot (const CompressedVector &originPos, const CVector &p, + float r = 1.0f, float a = 0.0f) + : compressedPos (originPos), pos (p), radius (r), angle (a) + { + state = OilSpotState::APPEAR; + } + + OilSpot & + operator= (const OilSpot &right) + { + if (this == &right) + { + return *this; + } + + lastVehicle = right.lastVehicle; + state = right.state; + compressedPos = right.compressedPos; + pos = right.pos; + radius = right.radius; + lifetime = right.lifetime; + angle = right.angle; + + return *this; + } + + void + Update (float dt) + { + if (IsDead ()) return; + lifetime += dt; + + if (lifetime >= APPEAR_TIME && state == OilSpotState::APPEAR) + { + state = OilSpotState::IDLE; + } + if (lifetime >= (MAX_LIFETIME - DISAPPEAR_TIME) + && state == OilSpotState::IDLE) + { + state = OilSpotState::DISAPPEAR; + } + + if (lifetime >= MAX_LIFETIME) state = OilSpotState::DEAD; + } + + void + Render () const + { + if (IsDead ()) return; + + unsigned char alpha = 255; + + if (state == OilSpotState::APPEAR) + { + alpha = (unsigned char) (255.0f * (lifetime / APPEAR_TIME)); + } + if (state == OilSpotState::DISAPPEAR) + { + alpha = (unsigned char) (255.0f + * ((MAX_LIFETIME - lifetime) + / DISAPPEAR_TIME)); + } + + Command (6, pos.x, pos.y, pos.z, + angle, radius, alpha, 0, + 0, 0); + } + + bool + IsDead () const + { + return state == OilSpotState::DEAD; + } + + bool + CheckCollision (CVehicle *vehicle) + { + if (state != OilSpotState::IDLE) return false; + if (!vehicle || IsDead () || lastVehicle == vehicle) + { + return false; + } + if (pointInRadius (pos, vehicle->GetPosition (), SPOT_SIZE * 0.5f)) + { + lastVehicle = vehicle; + return true; + } + + return false; + } +}; + +bool +operator<(const CompressedVector &lhs, const CompressedVector &rhs) +{ + if (lhs.x != rhs.x) + { + return lhs.x < rhs.x; + } + if (lhs.y != rhs.y) + { + return lhs.y < rhs.y; + } + return lhs.z < rhs.z; +} + +class OilOnRoadsEffect : public EffectBase +{ +private: + std::set pathOrigin; + std::set oilSpotsOrigin; + std::vector oilSpots; + + void pathSearch (); + void spawnNewOilSpot (EffectInstance *inst); + +public: + OilOnRoadsEffect () + { + oilSpots.reserve (128); + } + + void + OnTick (EffectInstance *inst) override + { + if (!GameUtil::IsPlayerSafe ()) return; + auto *player = FindPlayerPed (); + if (!player || player->m_nAreaCode) return; + + pathSearch (); + spawnNewOilSpot (inst); + + auto dt = float (GenericUtil::CalculateTick ()); + GameUtil::SetVehiclesToRealPhysics (); + + for (auto &spot : oilSpots) + { + if (!pointInRadius (spot.pos, player->GetPosition (), + AFFECTED_RADIUS)) + { + continue; + } + spot.Update (dt); + spot.Render (); + + for (auto *vehicle : CPools::ms_pVehiclePool) + { + if (vehicle->m_pDriver && spot.CheckCollision (vehicle)) + { + auto f = vehicle->m_fMass * dt * 0.001f + * -inst->Random (1.1f, 2.1f); + vehicle->ApplyMoveForce (-f * vehicle->m_vecMoveSpeed); + + float turnSpeed = inst->Random (-0.5f, 0.5f); + if (vehicle->m_pDriver == player) + { + turnSpeed = inst->Random (-0.12f, 0.12f); + } + vehicle->m_vecTurnSpeed.z + = turnSpeed * vehicle->m_vecMoveSpeed.Magnitude (); + } + } + + if (spot.IsDead ()) + { + oilSpotsOrigin.erase (spot.compressedPos); + } + } + + oilSpots.erase (std::remove_if (oilSpots.begin (), oilSpots.end (), + [] (const auto &spot) + { return spot.IsDead (); }), + oilSpots.end ()); + } +}; + +void +OilOnRoadsEffect::pathSearch () +{ + auto *player = FindPlayerPed (); + float offset = 256.0f; + auto pos = player->GetPosition (); + CVector nextPos[] = {{pos.x, pos.y + offset, pos.z}, + {pos.x, pos.y - offset, pos.z}, + {pos.x + offset, pos.y, pos.z}, + {pos.x - offset, pos.y, pos.z}, + {pos.x + offset, pos.y + offset, pos.z}, + {pos.x - offset, pos.y + offset, pos.z}, + {pos.x + offset, pos.y - offset, pos.z}, + {pos.x - offset, pos.y - offset, pos.z}}; + + for (int i = 0; i < std::size (nextPos); i++) + { + ThePaths.DoPathSearch (0, pos, {}, nextPos[i], resultNodes, &nodesCount, + MAX_NODES_COUNT, &gpsDistance, offset, nullptr, + offset, false, {}, false, false); + for (auto i = 0; i < nodesCount; i++) + { + auto p = ThePaths.GetPathNode (resultNodes[i])->m_vecPosn; + pathOrigin.emplace (p); + } + } +} + +void +OilOnRoadsEffect::spawnNewOilSpot (EffectInstance *inst) +{ + auto *player = FindPlayerPed (); + for (auto origin : pathOrigin) + { + auto originPos = origin.Uncompressed (); + if (!pointInRadius (originPos, player->GetPosition (), AFFECTED_RADIUS)) + { + continue; + } + + if (oilSpotsOrigin.find (origin) == oilSpotsOrigin.end ()) + { + originPos.x += inst->Random (-5.0f, 5.0f); + originPos.y += inst->Random (-5.0f, 5.0f); + originPos.z + = CWorld::FindGroundZForCoord (originPos.x, originPos.y); + originPos.z += 0.005f; // z fighting + auto radius = inst->Random (1.5f, 2.5f); + auto angle = inst->Random (0.0f, 360.0f) * 0.01745f; + + oilSpots.emplace_back (OilSpot (origin, originPos, radius, angle)); + + oilSpotsOrigin.emplace (origin); + } + } +} + +DEFINE_EFFECT (OilOnRoadsEffect, "effect_oil_on_roads", GROUP_HANDLING); \ No newline at end of file