summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/core/EventList.cpp18
-rw-r--r--src/core/Frontend.cpp43
-rw-r--r--src/core/Wanted.cpp125
-rw-r--r--src/core/Wanted.h4
-rw-r--r--src/core/World.cpp14
-rw-r--r--src/core/World.h1
-rw-r--r--src/core/config.h1
-rw-r--r--src/skel/win/win.cpp6
-rw-r--r--src/weapons/ShotInfo.cpp140
-rw-r--r--src/weapons/ShotInfo.h23
10 files changed, 329 insertions, 46 deletions
diff --git a/src/core/EventList.cpp b/src/core/EventList.cpp
index d72e32c4..d1c76f33 100644
--- a/src/core/EventList.cpp
+++ b/src/core/EventList.cpp
@@ -209,21 +209,9 @@ CEventList::ReportCrimeForEvent(eEventType type, int32 crimeId, bool copsDontCar
case EVENT_CAR_SET_ON_FIRE: crime = CRIME_VEHICLE_BURNED; break;
default: crime = CRIME_NONE; break;
}
-
-#ifdef VC_PED_PORTS
- if (crime == CRIME_HIT_PED && ((CPed*)crimeId)->IsPointerValid() &&
- FindPlayerPed()->m_pWanted->m_nWantedLevel == 0 && ((CPed*)crimeId)->bBeingChasedByPolice) {
-
- if(!((CPed*)crimeId)->DyingOrDead()) {
- sprintf(gString, "$50 Good Citizen Bonus!");
- AsciiToUnicode(gString, gUString);
- CMessages::AddBigMessage(gUString, 5000, 0);
- CWorld::Players[CWorld::PlayerInFocus].m_nMoney += 50;
- }
- } else
-#endif
- if(crime == CRIME_NONE)
- return;
+
+ if(crime == CRIME_NONE)
+ return;
CVector playerPedCoors = FindPlayerPed()->GetPosition();
CVector playerCoors = FindPlayerCoors();
diff --git a/src/core/Frontend.cpp b/src/core/Frontend.cpp
index 7f67d609..a469a215 100644
--- a/src/core/Frontend.cpp
+++ b/src/core/Frontend.cpp
@@ -1354,23 +1354,39 @@ void CMenuManager::DrawFrontEndNormal()
m_aFrontEndSprites[currentSprite].Draw(CRect(MENU_X_LEFT_ALIGNED(50.0f), MENU_Y(50.0f), MENU_X_RIGHT_ALIGNED(50.0f), SCREEN_SCALE_FROM_BOTTOM(95.0f)), CRGBA(255, 255, 255, m_nMenuFadeAlpha > 255 ? 255 : m_nMenuFadeAlpha));
+ static float fadeAlpha = 0.0f;
+ static int lastState = 0;
+
+ // reverseAlpha = PS2 fading (wait for 255->0, then change screen)
if (m_nMenuFadeAlpha < 255) {
- static uint32 LastFade = 0;
+ if (lastState == 1 && !reverseAlpha)
+ fadeAlpha = 0.f;
if (m_nMenuFadeAlpha <= 0 && reverseAlpha) {
reverseAlpha = false;
ChangeScreen(pendingScreen, pendingOption, true, false);
} else {
+ float timestep = CTimer::GetCurrentTimeInCycles() / (float)CTimer::GetCyclesPerMillisecond();
+
+ // +20 per every 33 ms (1000.f/30.f - original frame limiter fps)
if (!reverseAlpha)
- m_nMenuFadeAlpha += min((CTimer::GetTimeInMillisecondsPauseMode() - LastFade) / 33.0f, 1.0f) * 20.0f;
+ fadeAlpha += (timestep * 100.f) * 20.f / 33.f;
else
- m_nMenuFadeAlpha = max(0, m_nMenuFadeAlpha - min((CTimer::GetTimeInMillisecondsPauseMode() - LastFade) / 33.0f, 1.0f) * 30.0f);
+ fadeAlpha = max(0.0f, fadeAlpha - (timestep * 100.f) * 30.f / 33.f);
- LastFade = CTimer::GetTimeInMillisecondsPauseMode();
+ m_nMenuFadeAlpha = fadeAlpha;
}
+ lastState = 0;
} else {
- if (reverseAlpha)
- m_nMenuFadeAlpha -= 20;
+ if (lastState == 0) fadeAlpha = 255.f;
+
+ if (reverseAlpha) {
+ float timestep = CTimer::GetCurrentTimeInCycles() / (float)CTimer::GetCyclesPerMillisecond();
+ fadeAlpha -= (timestep * 100.f) * 30.f / 33.f;
+
+ m_nMenuFadeAlpha = fadeAlpha;
+ }
+ lastState = 1;
// TODO: what is this? waiting mouse?
if(field_518 == 4){
@@ -1568,13 +1584,20 @@ void CMenuManager::DrawFrontEndNormal()
}
if (m_nMenuFadeAlpha < 255) {
- static uint32 LastFade = 0;
- // Famous transparent menu bug. 33.0f = 1000.f/30.f (original frame limiter fps)
+ // Famous transparent menu bug
#ifdef FIX_BUGS
- m_nMenuFadeAlpha += min((CTimer::GetTimeInMillisecondsPauseMode() - LastFade) / 33.0f, 1.0f) * 20.0f;
- LastFade = CTimer::GetTimeInMillisecondsPauseMode();
+ static float fadeAlpha = 0.0f;
+ if (m_nMenuFadeAlpha == 0 && fadeAlpha > 1.0f) fadeAlpha = 0.0f;
+
+ float timestep = CTimer::GetCurrentTimeInCycles() / (float)CTimer::GetCyclesPerMillisecond();
+
+ // +20 per every 33 ms (1000.f/30.f - original frame limiter fps)
+ fadeAlpha += (timestep * 100.f) * 20.f / 33.f;
+ m_nMenuFadeAlpha = fadeAlpha;
#else
+ static uint32 LastFade = 0;
+
if(CTimer::GetTimeInMillisecondsPauseMode() - LastFade > 10){
m_nMenuFadeAlpha += 20;
LastFade = CTimer::GetTimeInMillisecondsPauseMode();
diff --git a/src/core/Wanted.cpp b/src/core/Wanted.cpp
index 7af753e8..29294a2b 100644
--- a/src/core/Wanted.cpp
+++ b/src/core/Wanted.cpp
@@ -7,19 +7,16 @@
#include "ZoneCull.h"
#include "Darkel.h"
#include "DMAudio.h"
+#include "CopPed.h"
#include "Wanted.h"
+#include "General.h"
int32 &CWanted::MaximumWantedLevel = *(int32*)0x5F7714; // 6
int32 &CWanted::nMaximumWantedLevel = *(int32*)0x5F7718; // 6400
-WRAPPER void CWanted::Reset() { EAXJMP(0x4AD790) };
-WRAPPER void CWanted::Update() { EAXJMP(0x4AD7B0) };
-
void
CWanted::Initialise()
{
- int i;
-
m_nChaos = 0;
m_nLastUpdateTime = 0;
m_nLastWantedLevelChange = 0;
@@ -34,10 +31,12 @@ CWanted::Initialise()
m_bArmyRequired = false;
m_fCrimeSensitivity = 1.0f;
m_nWantedLevel = 0;
- m_CopsBeatingSuspect = 0;
- for(i = 0; i < 10; i++)
+ m_CopsBeatingSuspect = 0;
+
+ for (int i = 0; i < ARRAY_SIZE(m_pCops); i++)
m_pCops[i] = nil;
- ClearQdCrimes();
+
+ ClearQdCrimes();
}
bool
@@ -61,7 +60,7 @@ CWanted::AreArmyRequired()
int32
CWanted::NumOfHelisRequired()
{
- if (m_bIgnoredByCops)
+ if (m_bIgnoredByCops || m_bIgnoredByEveryone)
return 0;
switch (m_nWantedLevel) {
@@ -79,9 +78,10 @@ CWanted::NumOfHelisRequired()
void
CWanted::SetWantedLevel(int32 level)
{
- ClearQdCrimes();
if (level > MaximumWantedLevel)
level = MaximumWantedLevel;
+
+ ClearQdCrimes();
switch (level) {
case 0:
m_nChaos = 0;
@@ -360,10 +360,107 @@ CWanted::WorkOutPolicePresence(CVector posn, float radius)
return numPolice;
}
+void
+CWanted::Update(void)
+{
+ if (CTimer::GetTimeInMilliseconds() - m_nLastUpdateTime > 1000) {
+ if (m_nWantedLevel > 1) {
+ m_nLastUpdateTime = CTimer::GetTimeInMilliseconds();
+ } else {
+ float radius = 18.0f;
+ CVector playerPos = FindPlayerCoors();
+ if (WorkOutPolicePresence(playerPos, radius) == 0) {
+ m_nLastUpdateTime = CTimer::GetTimeInMilliseconds();
+ m_nChaos = max(0, m_nChaos - 1);
+ UpdateWantedLevel();
+ }
+ }
+ UpdateCrimesQ();
+ bool orderMessedUp = false;
+ int currCopNum = 0;
+ bool foundEmptySlot = false;
+ for (int i = 0; i < ARRAY_SIZE(m_pCops); i++) {
+ if (m_pCops[i]) {
+ ++currCopNum;
+ if (foundEmptySlot)
+ orderMessedUp = true;
+ } else {
+ foundEmptySlot = true;
+ }
+ }
+ if (currCopNum != m_CurrentCops) {
+ printf("CopPursuit total messed up: re-setting\n");
+ m_CurrentCops = currCopNum;
+ }
+ if (orderMessedUp) {
+ printf("CopPursuit pointer list messed up: re-sorting\n");
+ bool fixed = true;
+ for (int i = 0; i < ARRAY_SIZE(m_pCops); i++) {
+ if (!m_pCops[i]) {
+ for (int j = i; j < ARRAY_SIZE(m_pCops); j++) {
+ if (m_pCops[j]) {
+ m_pCops[i] = m_pCops[j];
+ m_pCops[j] = nil;
+ fixed = false;
+ break;
+ }
+ }
+ if (fixed)
+ break;
+ }
+ }
+ }
+ }
+}
+
+void
+CWanted::ResetPolicePursuit(void)
+{
+ for(int i = 0; i < ARRAY_SIZE(m_pCops); i++) {
+ CCopPed *cop = m_pCops[i];
+ if (!cop)
+ continue;
+
+ cop->m_bIsInPursuit = false;
+ cop->m_objective = OBJECTIVE_NONE;
+ cop->m_prevObjective = OBJECTIVE_NONE;
+ cop->m_nLastPedState = PED_NONE;
+ if (!cop->DyingOrDead()) {
+ cop->SetWanderPath(CGeneral::GetRandomNumberInRange(0.0f, 8.0f));
+ }
+ m_pCops[i] = nil;
+ }
+ m_CurrentCops = 0;
+}
+
+void
+CWanted::Reset(void)
+{
+ ResetPolicePursuit();
+ Initialise();
+}
+
+void
+CWanted::UpdateCrimesQ(void)
+{
+ for(int i = 0; i < ARRAY_SIZE(m_aCrimes); i++) {
+
+ CCrimeBeingQd &crime = m_aCrimes[i];
+ if (crime.m_nType != CRIME_NONE) {
+ if (CTimer::GetTimeInMilliseconds() > crime.m_nTime + 500 && !crime.m_bReported) {
+ ReportCrimeNow(crime.m_nType, crime.m_vecPosn, crime.m_bPoliceDoesntCare);
+ crime.m_bReported = true;
+ }
+ if (CTimer::GetTimeInMilliseconds() > crime.m_nTime + 10000)
+ crime.m_nType = CRIME_NONE;
+ }
+ }
+}
+
STARTPATCHES
InjectHook(0x4AD6E0, &CWanted::Initialise, PATCH_JUMP);
-// InjectHook(0x4AD790, &CWanted::Reset, PATCH_JUMP);
-// InjectHook(0x4AD7B0, &CWanted::Update, PATCH_JUMP);
+ InjectHook(0x4AD790, &CWanted::Reset, PATCH_JUMP);
+ InjectHook(0x4AD7B0, &CWanted::Update, PATCH_JUMP);
InjectHook(0x4AD900, &CWanted::UpdateWantedLevel, PATCH_JUMP);
InjectHook(0x4AD9F0, &CWanted::RegisterCrime, PATCH_JUMP);
InjectHook(0x4ADA10, &CWanted::RegisterCrime_Immediately, PATCH_JUMP);
@@ -374,10 +471,10 @@ STARTPATCHES
InjectHook(0x4ADBC0, &CWanted::AreFbiRequired, PATCH_JUMP);
InjectHook(0x4ADBE0, &CWanted::AreArmyRequired, PATCH_JUMP);
InjectHook(0x4ADC00, &CWanted::NumOfHelisRequired, PATCH_JUMP);
-// InjectHook(0x4ADC40, &CWanted::ResetPolicePursuit, PATCH_JUMP);
+ InjectHook(0x4ADC40, &CWanted::ResetPolicePursuit, PATCH_JUMP);
InjectHook(0x4ADD00, &CWanted::WorkOutPolicePresence, PATCH_JUMP);
InjectHook(0x4ADF20, &CWanted::ClearQdCrimes, PATCH_JUMP);
InjectHook(0x4ADFD0, &CWanted::AddCrimeToQ, PATCH_JUMP);
-// InjectHook(0x4AE090, &CWanted::UpdateCrimesQ, PATCH_JUMP);
+ InjectHook(0x4AE090, &CWanted::UpdateCrimesQ, PATCH_JUMP);
InjectHook(0x4AE110, &CWanted::ReportCrimeNow, PATCH_JUMP);
ENDPATCHES
diff --git a/src/core/Wanted.h b/src/core/Wanted.h
index f6dbe8d0..9823529c 100644
--- a/src/core/Wanted.h
+++ b/src/core/Wanted.h
@@ -30,7 +30,7 @@ class CCrimeBeingQd
public:
eCrimeType m_nType;
uint32 m_nId;
- int32 m_nTime;
+ uint32 m_nTime;
CVector m_vecPosn;
bool m_bReported;
bool m_bPoliceDoesntCare;
@@ -78,6 +78,8 @@ public:
void ReportCrimeNow(eCrimeType type, const CVector &coors, bool policeDoesntCare);
void UpdateWantedLevel();
void Reset();
+ void ResetPolicePursuit();
+ void UpdateCrimesQ();
void Update();
bool IsIgnored(void) { return m_bIgnoredByCops || m_bIgnoredByEveryone; }
diff --git a/src/core/World.cpp b/src/core/World.cpp
index 95fd9673..4a0230ce 100644
--- a/src/core/World.cpp
+++ b/src/core/World.cpp
@@ -19,6 +19,7 @@
#include "Messages.h"
#include "Replay.h"
#include "Population.h"
+#include "Fire.h"
CColPoint *gaTempSphereColPoints = (CColPoint*)0x6E64C0; // [32]
@@ -1054,6 +1055,19 @@ CWorld::ExtinguishAllCarFiresInArea(CVector point, float range)
}
void
+CWorld::SetCarsOnFire(float x, float y, float z, float radius, CEntity *reason)
+{
+ int poolSize = CPools::GetVehiclePool()->GetSize();
+ for (int poolIndex = poolSize - 1; poolIndex >= 0; poolIndex--) {
+ CVehicle *veh = CPools::GetVehiclePool()->GetSlot(poolIndex);
+ if (veh && veh->m_status != STATUS_WRECKED && !veh->m_pCarFire && !veh->bFireProof) {
+ if (Abs(veh->GetPosition().z - z) < 5.0f && Abs(veh->GetPosition().x - x) < radius && Abs(veh->GetPosition().y - y) < radius)
+ gFireManager.StartFire(veh, reason, 0.8f, true);
+ }
+ }
+}
+
+void
CWorld::Process(void)
{
if (!(CTimer::GetFrameCounter() & 63))
diff --git a/src/core/World.h b/src/core/World.h
index 76dada2d..c4103eb2 100644
--- a/src/core/World.h
+++ b/src/core/World.h
@@ -131,6 +131,7 @@ public:
static void StopAllLawEnforcersInTheirTracks();
static void SetAllCarsCanBeDamaged(bool);
static void ExtinguishAllCarFiresInArea(CVector, float);
+ static void SetCarsOnFire(float, float, float, float, CEntity*);
static void Initialise();
static void AddParticles();
diff --git a/src/core/config.h b/src/core/config.h
index ea81e409..0d39550a 100644
--- a/src/core/config.h
+++ b/src/core/config.h
@@ -100,6 +100,7 @@ enum Config {
NUMPHONES = 50,
NUMPEDGROUPS = 31,
NUMMODELSPERPEDGROUP = 8,
+ NUMSHOTINFOS = 100,
NUMROADBLOCKS = 600,
diff --git a/src/skel/win/win.cpp b/src/skel/win/win.cpp
index 4e5dccff..7993426a 100644
--- a/src/skel/win/win.cpp
+++ b/src/skel/win/win.cpp
@@ -2056,13 +2056,7 @@ _WinMain(HINSTANCE instance,
{
GetWindowPlacement(PSGLOBAL(window), &wp);
- // Famous transparent menu bug. Also see the fix in Frontend.cpp
-#ifdef FIX_BUGS
- float ms = (float)CTimer::GetCurrentTimeInCycles() / (float)CTimer::GetCyclesPerMillisecond();
- if ((1000.0f / 100.0f) < ms && wp.showCmd != SW_SHOWMINIMIZED)
-#else
if (wp.showCmd != SW_SHOWMINIMIZED)
-#endif
RsEventHandler(rsFRONTENDIDLE, nil);
if ( !FrontEndMenuManager.m_bMenuActive || FrontEndMenuManager.m_bLoadingSavedGame )
diff --git a/src/weapons/ShotInfo.cpp b/src/weapons/ShotInfo.cpp
new file mode 100644
index 00000000..43d0579d
--- /dev/null
+++ b/src/weapons/ShotInfo.cpp
@@ -0,0 +1,140 @@
+#include "common.h"
+#include "patcher.h"
+#include "ShotInfo.h"
+#include "Entity.h"
+#include "Weapon.h"
+#include "World.h"
+#include "WeaponInfo.h"
+#include "General.h"
+#include "Timer.h"
+#include "Ped.h"
+#include "Fire.h"
+
+CShotInfo gaShotInfo[NUMSHOTINFOS];
+float CShotInfo::ms_afRandTable[20];
+
+// CShotInfo (&gaShotInfo)[100] = *(CShotInfo(*)[100])*(uintptr*)0x64F0D0;
+// float (&CShotInfo::ms_afRandTable)[20] = *(float(*)[20])*(uintptr*)0x6E9878;
+
+/*
+ Used for flamethrower. I don't know why it's name is CShotInfo.
+ Has no relation with any visual, just calculates the area fire affects
+ (including spreading and slowing of fire) and make entities burn/flee.
+*/
+
+void
+CShotInfo::Initialise()
+{
+ debug("Initialising CShotInfo...\n");
+ for(int i=0; i<ARRAY_SIZE(gaShotInfo); i++) {
+ gaShotInfo[i].m_inUse = false;
+ gaShotInfo[i].m_weapon = WEAPONTYPE_COLT45;
+ gaShotInfo[i].m_startPos = CVector(0.0f, 0.0f, 0.0f);
+ gaShotInfo[i].m_areaAffected = CVector(0.0f, 0.0f, 0.0f);
+ gaShotInfo[i].m_radius = 1.0f;
+ gaShotInfo[i].m_sourceEntity = nil;
+ gaShotInfo[i].m_timeout = 0;
+ }
+
+ // Not random for sure
+ float nextVal = -0.05f;
+ for (int i = 0; i < ARRAY_SIZE(ms_afRandTable); i++) {
+ ms_afRandTable[i] = nextVal;
+ nextVal += 0.005f;
+ }
+ debug("CShotInfo ready\n");
+}
+
+bool
+CShotInfo::AddShot(CEntity *sourceEntity, eWeaponType weapon, CVector startPos, CVector endPos)
+{
+ CWeaponInfo *weaponInfo = CWeaponInfo::GetWeaponInfo(weapon);
+
+ int slot;
+ for (slot = 0; slot < ARRAY_SIZE(gaShotInfo) && gaShotInfo[slot].m_inUse; slot++);
+
+ if (slot == ARRAY_SIZE(gaShotInfo))
+ return false;
+
+ gaShotInfo[slot].m_inUse = true;
+ gaShotInfo[slot].m_weapon = weapon;
+ gaShotInfo[slot].m_startPos = startPos;
+ gaShotInfo[slot].m_areaAffected = endPos - startPos;
+ gaShotInfo[slot].m_radius = weaponInfo->m_fRadius;
+
+ if (weaponInfo->m_fSpread != 0.0f) {
+ gaShotInfo[slot].m_areaAffected.x += CShotInfo::ms_afRandTable[CGeneral::GetRandomNumber() % ARRAY_SIZE(ms_afRandTable)] * weaponInfo->m_fSpread;
+ gaShotInfo[slot].m_areaAffected.y += CShotInfo::ms_afRandTable[CGeneral::GetRandomNumber() % ARRAY_SIZE(ms_afRandTable)] * weaponInfo->m_fSpread;
+ gaShotInfo[slot].m_areaAffected.z += CShotInfo::ms_afRandTable[CGeneral::GetRandomNumber() % ARRAY_SIZE(ms_afRandTable)];
+ }
+ gaShotInfo[slot].m_areaAffected.Normalise();
+ if (weaponInfo->m_bRandSpeed)
+ gaShotInfo[slot].m_areaAffected *= CShotInfo::ms_afRandTable[CGeneral::GetRandomNumber() % ARRAY_SIZE(ms_afRandTable)] + weaponInfo->m_fSpeed;
+ else
+ gaShotInfo[slot].m_areaAffected *= weaponInfo->m_fSpeed;
+
+ gaShotInfo[slot].m_sourceEntity = sourceEntity;
+ gaShotInfo[slot].m_timeout = CTimer::GetTimeInMilliseconds() + weaponInfo->m_fLifespan;
+
+ return true;
+}
+
+void
+CShotInfo::Shutdown()
+{
+ debug("Shutting down CShotInfo...\n");
+ debug("CShotInfo shut down\n");
+}
+
+void
+CShotInfo::Update()
+{
+ for (int slot = 0; slot < ARRAY_SIZE(gaShotInfo); slot++) {
+ CShotInfo &shot = gaShotInfo[slot];
+ if (shot.m_sourceEntity && shot.m_sourceEntity->IsPed() && !((CPed*)shot.m_sourceEntity)->IsPointerValid())
+ shot.m_sourceEntity = nil;
+
+ if (!shot.m_inUse)
+ continue;
+
+ CWeaponInfo *weaponInfo = CWeaponInfo::GetWeaponInfo(shot.m_weapon);
+ if (CTimer::GetTimeInMilliseconds() > shot.m_timeout)
+ shot.m_inUse = false;
+
+ if (weaponInfo->m_bSlowsDown)
+ shot.m_areaAffected *= pow(0.96, CTimer::GetTimeStep()); // FRAMERATE
+
+ if (weaponInfo->m_bExpands)
+ shot.m_radius += 0.075f * CTimer::GetTimeStep();
+
+ shot.m_startPos += CTimer::GetTimeStep() * shot.m_areaAffected;
+ if (shot.m_sourceEntity) {
+ assert(shot.m_sourceEntity->IsPed());
+ CPed *ped = (CPed*) shot.m_sourceEntity;
+ float radius = max(1.0f, shot.m_radius);
+
+ for (int i = 0; i < ped->m_numNearPeds; ++i) {
+ CPed *nearPed = ped->m_nearPeds[i];
+ if (nearPed->IsPointerValid()) {
+ if (nearPed->IsPedInControl() && (nearPed->GetPosition() - shot.m_startPos).MagnitudeSqr() < radius && !nearPed->bFireProof) {
+
+ if (!nearPed->IsPlayer()) {
+ nearPed->SetFindPathAndFlee(shot.m_sourceEntity, 10000);
+ nearPed->SetMoveState(PEDMOVE_SPRINT);
+ }
+ gFireManager.StartFire(nearPed, shot.m_sourceEntity, 0.8f, true);
+ }
+ }
+ }
+ }
+ if (!((CTimer::GetFrameCounter() + slot) & 3))
+ CWorld::SetCarsOnFire(shot.m_startPos.x, shot.m_startPos.y, shot.m_startPos.z, 4.0f, shot.m_sourceEntity);
+ }
+}
+
+STARTPATCHES
+ InjectHook(0x55BFF0, &CShotInfo::Update, PATCH_JUMP);
+ InjectHook(0x55BD70, &CShotInfo::AddShot, PATCH_JUMP);
+ InjectHook(0x55BC60, &CShotInfo::Initialise, PATCH_JUMP);
+ InjectHook(0x55BD50, &CShotInfo::Shutdown, PATCH_JUMP);
+ENDPATCHES \ No newline at end of file
diff --git a/src/weapons/ShotInfo.h b/src/weapons/ShotInfo.h
new file mode 100644
index 00000000..a5e5fd35
--- /dev/null
+++ b/src/weapons/ShotInfo.h
@@ -0,0 +1,23 @@
+#pragma once
+
+class CEntity;
+enum eWeaponType;
+
+class CShotInfo
+{
+public:
+ eWeaponType m_weapon;
+ CVector m_startPos;
+ CVector m_areaAffected;
+ float m_radius;
+ CEntity *m_sourceEntity;
+ float m_timeout;
+ bool m_inUse;
+
+ static float ms_afRandTable[20];
+
+ static void Initialise(void);
+ static bool AddShot(CEntity*, eWeaponType, CVector, CVector);
+ static void Shutdown(void);
+ static void Update(void);
+};