Created
March 16, 2021 04:46
-
-
Save user-grinch/cfc2817dfed726942eb7632552eb1613 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#define TRAM_MODEL 449 | |
#define DRIVER_MODEL 253 | |
#define DISTANCE_RANGE 10 | |
#define TRAM_WAIT_TIME 10000 // 10 seconds | |
#define DEBUG_STUFF | |
#ifdef DEBUG_STUFF// Only add in debug build | |
#include <fstream> | |
#include "CSprite.h" | |
#include "CFont.h" | |
std::ofstream flog = std::ofstream("Tram.log"); | |
#define LOG(x) flog << x << std::endl | |
#else | |
#define LOG(x) | |
#endif | |
#include <vector> | |
#include <time.h> | |
#include "plugin.h" | |
#include "CTrain.h" | |
#include "CWorld.h" | |
#include "CTimer.h" | |
#include "extensions\ScriptCommands.h" | |
#include "extensions\scripting\ScriptCommandNames.h" | |
#include "CStreaming.h" | |
#include "CCivilianPed.h" | |
#include "CCarEnterExit.h" | |
#include "CShadows.h" | |
#include "CClock.h" | |
#include "NodeName.h" | |
#include "CPad.h" | |
using namespace plugin; | |
CVector2D stops[] = | |
{ | |
#ifdef DEBUG_STUFF | |
{-2173, -68}, | |
{-2146, 31}, | |
{-2003, 484}, | |
{-2004, 272} | |
#else | |
{-2006.632f, 156.5977f}, | |
{-1952.414f, 603.3531f}, | |
{-1539.664f, 791.5298f}, | |
{-2001.679f, 880.2579f}, | |
{-1714.956f, 1327.946f}, | |
{-2264.926f, 1041.675f}, | |
{-2264.895f, 535.3699f}, | |
{-2251.925f, 129.5707f} | |
#endif | |
}; | |
enum TRAM_STATES { | |
TRAM_STOPPING, | |
TRAM_STOPPED, | |
TRAM_MOVING, | |
TRAM_UNKNOWN | |
}; | |
enum HEAD_LIGHT_TYPES { | |
LIGHT_SINGLE, | |
LIGHT_DOUBLE, | |
LIGHT_UNKNOWN | |
}; | |
enum PASSENGER_STATES { | |
PASSENGER_ENTER, | |
PASSENGER_LEAVE, | |
PASSENGER_ENTER_LEAVE, | |
PASSENGER_NO_CHANGE | |
}; | |
std::vector<CTrain*> ptrains; | |
struct TramData { | |
private: | |
CTrain* _ptr; | |
public: | |
TRAM_STATES state = TRAM_UNKNOWN; | |
uint stopped_timer = 0; | |
uint stop_index = -1; | |
float speed = 0; | |
PASSENGER_STATES pass_state = PASSENGER_NO_CHANGE; | |
HEAD_LIGHT_TYPES light_type = LIGHT_UNKNOWN; | |
TramData(CVehicle *vehicle) | |
{ | |
_ptr = (CTrain*)vehicle; | |
} | |
~TramData(){ | |
ptrains.erase(std::remove(ptrains.begin(), ptrains.end(), _ptr), ptrains.end()); | |
} | |
}; | |
VehicleExtendedData<TramData> tramdata; | |
int &OnAMissionFlag = *reinterpret_cast<int *>(0xA476AC); | |
#ifdef DEBUG_STUFF | |
// These debugging code it taken from Real Traffic Fix by Junior-Djjr | |
void GetEntityDimensions(CEntity *entity, CVector *outCornerA, CVector *outCornerB) | |
{ | |
CColModel *colModel = entity->GetColModel(); | |
outCornerA->x = colModel->m_boundBox.m_vecMin.x; | |
outCornerA->y = colModel->m_boundBox.m_vecMin.y; | |
outCornerA->z = colModel->m_boundBox.m_vecMin.z; | |
outCornerB->x = colModel->m_boundBox.m_vecMax.x; | |
outCornerB->y = colModel->m_boundBox.m_vecMax.y; | |
outCornerB->z = colModel->m_boundBox.m_vecMax.z; | |
} | |
CVector GetWorldCoordWithOffset(CEntity *entity, CVector *offset) | |
{ | |
return (Multiply3x3(*entity->m_matrix, *offset) + entity->m_matrix->pos); | |
} | |
// For debugging | |
void DrawDebugStringOffset(CVehicle *vehicle, float& zOffset, std::string text) | |
{ | |
CVector modelMin; | |
CVector modelMax; | |
GetEntityDimensions(vehicle, &modelMin, &modelMax); | |
CVector fontPosOffset = { 0.0, 0.0, modelMax.z + zOffset + 0.5f }; | |
CVector fontPos3D = GetWorldCoordWithOffset(vehicle, &fontPosOffset); | |
RwV3d fontPos2D; float w, h; | |
if (CSprite::CalcScreenCoors({ fontPos3D.x, fontPos3D.y, fontPos3D.z}, &fontPos2D, &w, &h, true, true)) | |
{ | |
float fontSizeX = w / 90.0f; | |
float fontSizeY = h / 100.0f; | |
if (fontSizeX > 0.2) | |
{ | |
CFont::SetBackground(false, false); | |
CFont::SetJustify(false); | |
CFont::SetProportional(false); | |
CFont::SetFontStyle(2); | |
CFont::SetColor({255,255,255, 255}); | |
CFont::SetScale(fontSizeX, fontSizeY); | |
if (fontSizeX > 0.4) | |
CFont::SetEdge(1); | |
CFont::PrintString(fontPos2D.x, fontPos2D.y, &text[0]); | |
} | |
} | |
zOffset += 0.35f; | |
} | |
#endif | |
bool IsFemale(int modelIndex) { | |
return ((bool(__cdecl *)(int))0x611490)(modelIndex); | |
} | |
bool IsOnMission() | |
{ | |
return FindPlayerPed()->CanPlayerStartMission() && !*(patch::Get<char*>(0x5D5380, false) + OnAMissionFlag); | |
} | |
// partial implemention of opcode 0AB5 (STORE_CLOSEST_ENTITIES) | |
// https://github.com/cleolibrary/CLEO4/blob/916d400f4a731ba1dd0ff16e52bdb056f42b7038/source/CCustomOpcodeSystem.cpp#L1671 | |
void AddNearbyPedAsRandomPassenger(CTrain *pveh) | |
{ | |
CPlayerPed *player = FindPlayerPed(); | |
CPedIntelligence * pedintel; | |
if (player && (pedintel = player->m_pIntelligence)) | |
{ | |
CPed *ped = nullptr; | |
for (int i = 0; i < 16; i++) | |
{ | |
ped = (CPed*)pedintel->m_pedScanner.m_apEntities[i]; | |
if (ped && ped != player && (ped->m_nCreatedBy & 0xFF) == 1 && !ped->m_nPedFlags.bFadeOut) | |
break; | |
ped = nullptr; | |
} | |
if (ped) | |
{ | |
for(int i=0; i != 6; ++i) | |
{ | |
if (!pveh->m_apPassengers[i]) | |
{ | |
Command<Commands::TASK_ENTER_CAR_AS_PASSENGER>(CPools::GetPedRef(ped),CPools::GetVehicleRef(pveh),8000,i); | |
break; | |
} | |
} | |
} | |
} | |
} | |
CVehicle* GetClosestVehicle(CPlayerPed* player) | |
{ | |
CPedIntelligence *pedintel; | |
if (player && (pedintel = player->m_pIntelligence)) | |
{ | |
CVehicle *veh = nullptr; | |
for (int i = 0; i < 16; i++) | |
{ | |
veh = (CVehicle*)pedintel->m_vehicleScanner.m_apEntities[i]; | |
if (veh && !veh->m_nVehicleFlags.bFadeOut) | |
break; | |
veh = nullptr; | |
} | |
return veh; | |
} | |
return nullptr; | |
} | |
void EnterPassengers(CTrain *pveh, int amount) | |
{ | |
int carraige = rand() % 2; // 0 -> 1 | |
if (carraige == 1 || pveh->m_pNextCarriage == NULL) | |
{ | |
for (int i = 0; i != amount; ++i) | |
AddNearbyPedAsRandomPassenger(pveh); | |
} | |
else | |
{ | |
for (int i =0; i != amount; ++i) | |
AddNearbyPedAsRandomPassenger(pveh->m_pNextCarriage); | |
} | |
} | |
void LeavePassengers(CTrain *pveh, int amount) | |
{ | |
// Let's see if there's a carraige | |
if (pveh->m_pNextCarriage == NULL) // Doesn't have a carriage | |
{ | |
for (int i =0; i != amount; ++i) | |
{ | |
CPed *ped = pveh->PickRandomPassenger(); | |
if (ped) | |
Command<Commands::TASK_LEAVE_ANY_CAR>(CPools::GetPedRef(ped)); | |
} | |
} | |
else // has a carraige, so let's take that into account | |
{ | |
for (int i =0; i != amount; ++i) | |
{ | |
int carraige = rand() % ((2 - 1)+1) + 1; // 1 -> 2 | |
CPed *ped = nullptr; | |
if (carraige == 1) | |
ped = pveh->PickRandomPassenger(); | |
else | |
ped = pveh->m_pNextCarriage->PickRandomPassenger(); | |
if (ped) | |
Command<Commands::TASK_LEAVE_ANY_CAR>(CPools::GetPedRef(ped)); | |
} | |
} | |
} | |
void ProcessPassengerEnterExit(CTrain *pveh) | |
{ | |
int status = rand() % 4; // 0 -> 3 | |
TramData &data = tramdata.Get((CVehicle*)pveh); | |
EnterPassengers(pveh, rand() % 4 + 1); | |
return; | |
switch(status) | |
{ | |
case PASSENGER_ENTER: | |
{ | |
EnterPassengers(pveh, rand() % 3 + 1); | |
data.pass_state = PASSENGER_ENTER; | |
break; | |
} | |
case PASSENGER_LEAVE: | |
{ | |
LeavePassengers(pveh, rand() % 3 + 1); | |
data.pass_state = PASSENGER_LEAVE; | |
break; | |
} | |
case PASSENGER_ENTER_LEAVE: | |
{ | |
int amount = rand() % 4; // 0 -> 3 | |
if (amount > 0) | |
EnterPassengers(pveh, amount); | |
amount = rand() % 2; // 0 -> 1 | |
if (amount > 0) | |
LeavePassengers(pveh, amount); | |
data.pass_state = PASSENGER_ENTER_LEAVE; | |
break; | |
} | |
default: | |
{ | |
data.pass_state = PASSENGER_NO_CHANGE; | |
break; | |
} | |
} | |
} | |
void FillTramWithPassengers(CVehicle *pveh) | |
{ | |
int max_seats = pveh->m_nMaxPassengers; | |
// Get random ped models and add them as passengers | |
for (int i = 2; i != max_seats; ++i) | |
{ | |
int model = 20 + rand() % ((40 + 1)-20); // 20 -> 40 | |
if (model == 26) // that bag guy model goes through the tram | |
model = 23; | |
int chance = rand() % 4; // 0-> 3, 25% chance of a ped being added | |
if (chance == 0) // Let's skip if the value is 0 to create few empty seats | |
continue; | |
CStreaming::RequestModel(model, 0); | |
CStreaming::LoadAllRequestedModels(false); | |
CPed *ped = new CCivilianPed(IsFemale(model) ? PED_TYPE_CIVFEMALE : PED_TYPE_CIVMALE, model); | |
if (ped) { | |
ped->SetPosn(CVector(0,0,0)); | |
CWorld::Add(ped); | |
CCarEnterExit::SetPedInCarDirect(ped,pveh,i,false); | |
ped->m_nCreatedBy = 2; | |
ped->m_nPedFlags.bDontFight = true; | |
ped->m_nPedState = PEDSTATE_IDLE; | |
ped->m_nPedFlags.bHasAScriptBrain = false; | |
} | |
} | |
} | |
void InitTram(CVector posn, bool clockwiseDirection, unsigned int trainType, CTrain**outFirstCarriage, CTrain**outLastCarriage, int nodeIndex, int trackId, bool isMissionTrain) | |
{ | |
// Let's create the tram first | |
CTrain::CreateMissionTrain(posn,clockwiseDirection, trainType, outFirstCarriage, outLastCarriage, nodeIndex, trackId, isMissionTrain); | |
CTrain *pveh = outFirstCarriage[0]; // get the tram engine here | |
if (pveh && pveh->m_nModelIndex == TRAM_MODEL) | |
{ | |
TramData &data = tramdata.Get((CVehicle*)pveh); | |
CPlayerPed *player = FindPlayerPed(); | |
ptrains.push_back(pveh); | |
data.state = TRAM_MOVING; | |
if (!pveh->m_pDriver) | |
{ | |
CStreaming::RequestModel(DRIVER_MODEL, 0); | |
CStreaming::LoadAllRequestedModels(false); | |
CPed *driver = new CCivilianPed(PED_TYPE_CIVMALE, DRIVER_MODEL); | |
if (driver) { | |
driver->SetPosn(CVector(0,0,0)); | |
CWorld::Add(driver); | |
CCarEnterExit::SetPedInCarDirect(driver,pveh,0,true); | |
pveh->m_pDriver = driver; | |
driver->m_nCreatedBy = 2; | |
driver->m_nPedFlags.bDontFight = true; | |
driver->m_nPedState = PEDSTATE_IDLE; | |
driver->m_nPedFlags.bHasAScriptBrain = false; | |
FillTramWithPassengers(pveh); | |
if (pveh->m_pNextCarriage) | |
{ | |
// Remove the driver from the carrainge | |
pveh->m_pNextCarriage->RemoveDriver(true); | |
FillTramWithPassengers(pveh->m_pNextCarriage); | |
} | |
} | |
} | |
} | |
} | |
void ProcessTram(CTrain *pveh, TramData &data) | |
{ | |
if (pveh->m_pDriver == FindPlayerPed() || IsOnMission()) | |
return; | |
if (pveh->m_pDriver) | |
{ | |
int size = sizeof(stops) / sizeof(CVector2D); | |
for (int i = 0; i != size; ++i) | |
{ | |
float distance = DistanceBetweenPoints(pveh->GetPosition(),stops[i]); | |
if (distance < DISTANCE_RANGE && data.stop_index != i) | |
{ | |
data.state = TRAM_STOPPING; | |
data.stop_index = i; | |
data.stopped_timer = CTimer::m_snTimeInMilliseconds; | |
ProcessPassengerEnterExit(pveh); | |
} | |
if (data.state == TRAM_STOPPING) | |
{ | |
if (abs(pveh->m_fTrainSpeed) < 0.005f) | |
{ | |
data.state = TRAM_STOPPED; | |
data.stopped_timer = CTimer::m_snTimeInMilliseconds; | |
data.speed = 0; | |
} | |
else | |
{ | |
if ((CTimer::m_snTimeInMilliseconds - data.stopped_timer) < 1000) | |
{ | |
if (data.speed == 0) | |
data.speed = pveh->m_fTrainSpeed; | |
else | |
{ | |
// Speed can be negative or positive based on direction | |
if (pveh->m_fTrainSpeed > 0) | |
data.speed -= 0.0005f; | |
else | |
data.speed += 0.0005f; | |
} | |
data.stopped_timer = CTimer::m_snTimeInMilliseconds; | |
} | |
pveh->m_fTrainSpeed = data.speed; | |
} | |
} | |
if (data.state == TRAM_STOPPED) | |
{ | |
if ((CTimer::m_snTimeInMilliseconds - data.stopped_timer) > TRAM_WAIT_TIME) | |
data.state = TRAM_MOVING; | |
else | |
pveh->m_fTrainSpeed = 0.0; | |
} | |
} | |
} | |
else | |
pveh->m_fTrainSpeed = 0.0; | |
#ifdef DEBUG_STUFF | |
float zOffset = 0.0f; | |
std::string state; | |
if (data.state == TRAM_MOVING) | |
state = "Moving"; | |
if (data.state == TRAM_STOPPED) | |
state = "Stopped"; | |
if (data.state == TRAM_STOPPING) | |
state = "Stopping"; | |
if (data.state == TRAM_UNKNOWN) | |
state = "Unknown"; | |
DrawDebugStringOffset(pveh,zOffset,state); | |
int passengers = pveh->m_nNumPassengers; | |
if (pveh->m_pNextCarriage) | |
passengers += pveh->m_pNextCarriage->m_nNumPassengers; | |
std::string pass_text = "Pass: " + std::to_string(passengers); | |
DrawDebugStringOffset(pveh,zOffset,pass_text); | |
if (data.pass_state == PASSENGER_ENTER) | |
state = "Enter"; | |
if (data.pass_state == PASSENGER_LEAVE) | |
state = "Leave"; | |
if (data.pass_state == PASSENGER_ENTER_LEAVE) | |
state = "EnterLeave"; | |
if (data.pass_state == PASSENGER_NO_CHANGE) | |
state = "Nochange"; | |
DrawDebugStringOffset(pveh,zOffset,state); | |
DrawDebugStringOffset(pveh,zOffset,std::to_string(pveh->m_fTrainSpeed)); | |
#endif | |
} | |
void ProcessHeadlights(CTrain *pTrain, TramData& data) | |
{ | |
if (pTrain->m_nModelIndex == TRAM_MODEL && pTrain->m_pPrevCarriage == NULL | |
&& pTrain->m_nVehicleFlags.bEngineOn && !pTrain->m_nVehicleFlags.bEngineBroken) | |
{ | |
int hour = CClock::ms_nGameClockHours; | |
if (hour > 20 || hour < 8) | |
{ | |
CVector Pos = pTrain->GetColModel()->m_boundBox.m_vecMin; | |
CVector center_offset = CVector(0.0f, 12.0f, 0.0f); | |
CVector center = pTrain->TransformFromObjectSpace(center_offset); | |
CVector up = pTrain->TransformFromObjectSpace(CVector(0.0f, -Pos.y + 3.0f + center_offset.y, 0.0f)) - center; | |
CVector right = pTrain->TransformFromObjectSpace(CVector(Pos.x - 1.5f + center_offset.x , center_offset.y, 0.0f)) - center; | |
if (data.light_type == LIGHT_SINGLE) | |
CShadows::StoreShadowToBeRendered(SHADOW_ADDITIVE, gpShadowHeadLightsTex2, ¢er, up.x, up.y, right.x, right.y, 80, 80, 80, 80, 2.0f, false, 1.0f, 0, true); | |
else | |
CShadows::StoreShadowToBeRendered(SHADOW_ADDITIVE, gpShadowHeadLightsTex, ¢er, up.x, up.y, right.x, right.y, 80, 80, 80, 80, 2.0f, false, 1.0f, 0, true); | |
} | |
} | |
} | |
void ProcessCarriageNodes(RwFrame *frame, CTrain* pVeh) | |
{ | |
if(frame) | |
{ | |
const std::string name = GetFrameNodeName(frame); | |
if (name == ("fc_spk")) // braking | |
{ | |
CVector pos = frame->ltm.pos; | |
Command<Commands::ADD_SPARKS>(pos.x,pos.y,pos.z,1.0,0.0,0.0,40); | |
} | |
if (RwFrame * newFrame = frame->child) | |
ProcessCarriageNodes(newFrame, pVeh); | |
if (RwFrame * newFrame = frame->next) | |
ProcessCarriageNodes(newFrame, pVeh); | |
} | |
return; | |
} | |
void ProcessNodes(RwFrame *frame, CTrain* pVeh, TramData &data) | |
{ | |
if(frame) | |
{ | |
const std::string name = GetFrameNodeName(frame); | |
if (name == "onehl") | |
data.light_type = LIGHT_SINGLE; | |
if (name == "twohls") | |
data.light_type = LIGHT_DOUBLE; | |
if (name == "dis_hl") | |
pVeh->ms_forceVehicleLightsOff = true; | |
if (name == ("fc_spk") && pVeh->m_fTrainBrake == 255 && abs(pVeh->m_fTrainSpeed) > 0.4f) // braking | |
{ | |
CVector pos = frame->ltm.pos; | |
Command<Commands::ADD_SPARKS>(pos.x,pos.y,pos.z,1.0,0.0,0.0,40); | |
if (pVeh->m_pNextCarriage) | |
ProcessCarriageNodes((RwFrame *)pVeh->m_pNextCarriage->m_pRwClump->object.parent, pVeh->m_pNextCarriage); | |
} | |
if (RwFrame * newFrame = frame->child) | |
ProcessNodes(newFrame, pVeh, data); | |
if (RwFrame * newFrame = frame->next) | |
ProcessNodes(newFrame, pVeh, data); | |
} | |
return; | |
} | |
class Tram { | |
public: | |
Tram() { | |
Events::initGameEvent += []() | |
{ | |
LOG("Log started"); | |
srand(CTimer::m_snTimeInMilliseconds); | |
// CTrain::DoTrainGenerationAndRemoval(), CTrain::CreateMissionTrain() | |
patch::RedirectCall(0x6F7C2C, &InitTram, false); | |
}; | |
Events::vehicleRenderEvent += [](CVehicle *veh) | |
{ | |
if (veh->m_nModelIndex == TRAM_MODEL) | |
{ | |
TramData &data = tramdata.Get(veh); | |
CTrain *pTrain = (CTrain*)veh; | |
float z = 0; | |
if (pTrain->m_pDriver == FindPlayerPed()) | |
DrawDebugStringOffset(pTrain,z,std::to_string(pTrain->m_fTrainSpeed)); | |
if (std::find(ptrains.begin(), ptrains.end(), pTrain) != ptrains.end()) | |
ProcessTram(pTrain, data); | |
ProcessNodes((RwFrame *)pTrain->m_pRwClump->object.parent, pTrain, data); | |
ProcessHeadlights(pTrain, data); | |
} | |
}; | |
Events::processScriptsEvent += []() | |
{ | |
if (KeyPressed(VK_TAB)) | |
CTrain::DoTrainGenerationAndRemoval(); | |
if (CPad::GetPad(0)->GetExitVehicle()) | |
{ | |
CPlayerPed *player = FindPlayerPed(); | |
CVehicle *pVeh = GetClosestVehicle(player); | |
if (pVeh->m_nModelIndex == TRAM_MODEL) | |
{ | |
if (pVeh->m_pDriver == player) | |
Command<Commands::TASK_LEAVE_CAR>(CPools::GetPedRef(player),CPools::GetVehicleRef(pVeh)); | |
else | |
Command<Commands::TASK_ENTER_CAR_AS_DRIVER>(player,pVeh,-2); | |
} | |
} | |
}; | |
} | |
} tram; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment