Skip to content

Instantly share code, notes, and snippets.

@user-grinch
Created March 16, 2021 04:46
Show Gist options
  • Save user-grinch/cfc2817dfed726942eb7632552eb1613 to your computer and use it in GitHub Desktop.
Save user-grinch/cfc2817dfed726942eb7632552eb1613 to your computer and use it in GitHub Desktop.
#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, &center, 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, &center, 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