Skip to content

Commit 5dff203

Browse files
authored
bugfix(weapon): Fix unreliability of historic bonus weapons (#1727)
This change fixes historic bonus weapons not triggering reliably. The issue is that whenever a historic bonus weapon is triggered (e.g. a firestorm) it resets the historic bonus count for all weapons using the same template. This means that if a firestorm is triggered while another firestorm is half way to triggering, that other firestorm's progress will be reset. In addition, historic damage instances now more accurately expire based on the weapon template's HistoricBonusTime rather than the global HistoricDamageLimit. Historic damage involved in triggering a historic weapon cannot be used in subsequent attempts.
1 parent 59e5d9b commit 5dff203

File tree

4 files changed

+196
-24
lines changed

4 files changed

+196
-24
lines changed

Generals/Code/GameEngine/Include/GameLogic/Weapon.h

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -317,9 +317,10 @@ struct HistoricWeaponDamageInfo
317317
// The time and location this weapon was fired
318318
UnsignedInt frame;
319319
Coord3D location;
320+
UnsignedInt triggerId; ///< Unique Id assigned to any grouped damage instances
320321

321322
HistoricWeaponDamageInfo(UnsignedInt f, const Coord3D& l) :
322-
frame(f), location(l)
323+
frame(f), location(l), triggerId(0)
323324
{
324325
}
325326
};
@@ -457,6 +458,8 @@ class WeaponTemplate : public MemoryPoolObject
457458
// actually deal out the damage.
458459
void dealDamageInternal(ObjectID sourceID, ObjectID victimID, const Coord3D *pos, const WeaponBonus& bonus, Bool isProjectileDetonation) const;
459460
void trimOldHistoricDamage() const;
461+
void trimTriggeredHistoricDamage() const;
462+
void processHistoricDamage(const Object* source, const Coord3D* pos) const;
460463

461464
private:
462465

@@ -533,6 +536,7 @@ class WeaponTemplate : public MemoryPoolObject
533536
Real m_infantryInaccuracyDist; ///< When this weapon is used against infantry, it can randomly miss by as much as this distance.
534537
UnsignedInt m_suspendFXDelay; ///< The fx can be suspended for any delay, in frames, then they will execute as normal
535538
mutable HistoricWeaponDamageList m_historicDamage;
539+
mutable UnsignedInt m_historicDamageTriggerId;
536540
};
537541

538542
// ---------------------------------------------------------

Generals/Code/GameEngine/Source/GameLogic/Object/Weapon.cpp

Lines changed: 93 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -309,6 +309,8 @@ WeaponTemplate::WeaponTemplate() : m_nextTemplate(NULL)
309309
m_continueAttackRange = 0.0f;
310310
m_infantryInaccuracyDist = 0.0f;
311311
m_suspendFXDelay = 0;
312+
313+
m_historicDamageTriggerId = 0;
312314
}
313315

314316
//-------------------------------------------------------------------------------------------------
@@ -1093,6 +1095,7 @@ UnsignedInt WeaponTemplate::fireWeaponTemplate
10931095
}
10941096

10951097
//-------------------------------------------------------------------------------------------------
1098+
#if RETAIL_COMPATIBLE_CRC
10961099
void WeaponTemplate::trimOldHistoricDamage() const
10971100
{
10981101
UnsignedInt expirationDate = TheGameLogic->getFrame() - TheGlobalData->m_historicDamageLimit;
@@ -1112,6 +1115,40 @@ void WeaponTemplate::trimOldHistoricDamage() const
11121115
}
11131116
}
11141117
}
1118+
#else
1119+
void WeaponTemplate::trimOldHistoricDamage() const
1120+
{
1121+
if (m_historicDamage.empty())
1122+
return;
1123+
1124+
const UnsignedInt currentFrame = TheGameLogic->getFrame();
1125+
const UnsignedInt expirationFrame = currentFrame - m_historicBonusTime;
1126+
1127+
HistoricWeaponDamageList::iterator it = m_historicDamage.begin();
1128+
1129+
while (it != m_historicDamage.end())
1130+
{
1131+
if (it->frame <= expirationFrame)
1132+
it = m_historicDamage.erase(it);
1133+
else
1134+
break;
1135+
}
1136+
}
1137+
#endif
1138+
1139+
//-------------------------------------------------------------------------------------------------
1140+
void WeaponTemplate::trimTriggeredHistoricDamage() const
1141+
{
1142+
HistoricWeaponDamageList::iterator it = m_historicDamage.begin();
1143+
1144+
while (it != m_historicDamage.end())
1145+
{
1146+
if (it->triggerId == m_historicDamageTriggerId)
1147+
it = m_historicDamage.erase(it);
1148+
else
1149+
++it;
1150+
}
1151+
}
11151152

11161153
//-------------------------------------------------------------------------------------------------
11171154
static Bool is2DDistSquaredLessThan(const Coord3D& a, const Coord3D& b, Real distSqr)
@@ -1121,27 +1158,20 @@ static Bool is2DDistSquaredLessThan(const Coord3D& a, const Coord3D& b, Real dis
11211158
}
11221159

11231160
//-------------------------------------------------------------------------------------------------
1124-
void WeaponTemplate::dealDamageInternal(ObjectID sourceID, ObjectID victimID, const Coord3D *pos, const WeaponBonus& bonus, Bool isProjectileDetonation) const
1161+
#if RETAIL_COMPATIBLE_CRC
1162+
void WeaponTemplate::processHistoricDamage(const Object* source, const Coord3D* pos) const
11251163
{
1126-
if (sourceID == 0) // must have a source
1127-
return;
1128-
1129-
if (victimID == 0 && pos == NULL) // must have some sort of destination
1130-
return;
1131-
1132-
Object *source = TheGameLogic->findObjectByID(sourceID); // might be null...
1133-
11341164
//
11351165
/** @todo We need to rewrite the historic stuff ... if you fire 5 missiles, and the 5th,
11361166
// one creates a firestorm ... and then half a second later another volley of 5 missiles
11371167
// come in, the second wave of 5 missiles would all do a historic weapon, making 5 more
11381168
// firestorms (CBD) */
11391169
//
11401170

1141-
trimOldHistoricDamage();
1142-
11431171
if( m_historicBonusCount > 0 && m_historicBonusWeapon != this )
11441172
{
1173+
trimOldHistoricDamage();
1174+
11451175
Real radSqr = m_historicBonusRadius * m_historicBonusRadius;
11461176
Int count = 0;
11471177
UnsignedInt frameNow = TheGameLogic->getFrame();
@@ -1176,6 +1206,58 @@ void WeaponTemplate::dealDamageInternal(ObjectID sourceID, ObjectID victimID, co
11761206
}
11771207

11781208
}
1209+
}
1210+
#else
1211+
void WeaponTemplate::processHistoricDamage(const Object* source, const Coord3D* pos) const
1212+
{
1213+
if (m_historicBonusCount > 0 && m_historicBonusWeapon != this)
1214+
{
1215+
trimOldHistoricDamage();
1216+
1217+
++m_historicDamageTriggerId;
1218+
1219+
const Int requiredCount = m_historicBonusCount - 1; // minus 1 since we include ourselves implicitly
1220+
if (m_historicDamage.size() >= requiredCount)
1221+
{
1222+
const Real radSqr = m_historicBonusRadius * m_historicBonusRadius;
1223+
Int count = 0;
1224+
1225+
for (HistoricWeaponDamageList::iterator it = m_historicDamage.begin(); it != m_historicDamage.end(); ++it)
1226+
{
1227+
if (is2DDistSquaredLessThan(*pos, it->location, radSqr))
1228+
{
1229+
// This one is close enough in time and distance, so count it. This is tracked by template since it applies
1230+
// across units, so don't try to clear historicDamage on success in here.
1231+
it->triggerId = m_historicDamageTriggerId;
1232+
1233+
if (++count == requiredCount)
1234+
{
1235+
TheWeaponStore->createAndFireTempWeapon(m_historicBonusWeapon, source, pos);
1236+
trimTriggeredHistoricDamage();
1237+
return;
1238+
}
1239+
}
1240+
}
1241+
}
1242+
1243+
// add AFTER checking for historic stuff
1244+
m_historicDamage.push_back(HistoricWeaponDamageInfo(TheGameLogic->getFrame(), *pos));
1245+
}
1246+
}
1247+
#endif
1248+
1249+
//-------------------------------------------------------------------------------------------------
1250+
void WeaponTemplate::dealDamageInternal(ObjectID sourceID, ObjectID victimID, const Coord3D *pos, const WeaponBonus& bonus, Bool isProjectileDetonation) const
1251+
{
1252+
if (sourceID == 0) // must have a source
1253+
return;
1254+
1255+
if (victimID == 0 && pos == NULL) // must have some sort of destination
1256+
return;
1257+
1258+
Object *source = TheGameLogic->findObjectByID(sourceID); // might be null...
1259+
1260+
processHistoricDamage(source, pos);
11791261

11801262
//DEBUG_LOG(("WeaponTemplate::dealDamageInternal: dealing damage %s at frame %d",m_name.str(),TheGameLogic->getFrame()));
11811263

GeneralsMD/Code/GameEngine/Include/GameLogic/Weapon.h

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -328,9 +328,10 @@ struct HistoricWeaponDamageInfo
328328
// The time and location this weapon was fired
329329
UnsignedInt frame;
330330
Coord3D location;
331+
UnsignedInt triggerId; ///< Unique Id assigned to any grouped damage instances
331332

332333
HistoricWeaponDamageInfo(UnsignedInt f, const Coord3D& l) :
333-
frame(f), location(l)
334+
frame(f), location(l), triggerId(0)
334335
{
335336
}
336337
};
@@ -477,6 +478,8 @@ class WeaponTemplate : public MemoryPoolObject
477478
// actually deal out the damage.
478479
void dealDamageInternal(ObjectID sourceID, ObjectID victimID, const Coord3D *pos, const WeaponBonus& bonus, Bool isProjectileDetonation) const;
479480
void trimOldHistoricDamage() const;
481+
void trimTriggeredHistoricDamage() const;
482+
void processHistoricDamage(const Object* source, const Coord3D* pos) const;
480483

481484
private:
482485

@@ -561,6 +564,7 @@ class WeaponTemplate : public MemoryPoolObject
561564
Bool m_dieOnDetonate;
562565

563566
mutable HistoricWeaponDamageList m_historicDamage;
567+
mutable UnsignedInt m_historicDamageTriggerId;
564568
};
565569

566570
// ---------------------------------------------------------

GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Weapon.cpp

Lines changed: 93 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -322,6 +322,8 @@ WeaponTemplate::WeaponTemplate() : m_nextTemplate(NULL)
322322
m_damageStatusType = OBJECT_STATUS_NONE;
323323
m_suspendFXDelay = 0;
324324
m_dieOnDetonate = FALSE;
325+
326+
m_historicDamageTriggerId = 0;
325327
}
326328

327329
//-------------------------------------------------------------------------------------------------
@@ -1171,6 +1173,7 @@ UnsignedInt WeaponTemplate::fireWeaponTemplate
11711173
}
11721174

11731175
//-------------------------------------------------------------------------------------------------
1176+
#if RETAIL_COMPATIBLE_CRC
11741177
void WeaponTemplate::trimOldHistoricDamage() const
11751178
{
11761179
UnsignedInt expirationDate = TheGameLogic->getFrame() - TheGlobalData->m_historicDamageLimit;
@@ -1190,6 +1193,40 @@ void WeaponTemplate::trimOldHistoricDamage() const
11901193
}
11911194
}
11921195
}
1196+
#else
1197+
void WeaponTemplate::trimOldHistoricDamage() const
1198+
{
1199+
if (m_historicDamage.empty())
1200+
return;
1201+
1202+
const UnsignedInt currentFrame = TheGameLogic->getFrame();
1203+
const UnsignedInt expirationFrame = currentFrame - m_historicBonusTime;
1204+
1205+
HistoricWeaponDamageList::iterator it = m_historicDamage.begin();
1206+
1207+
while (it != m_historicDamage.end())
1208+
{
1209+
if (it->frame <= expirationFrame)
1210+
it = m_historicDamage.erase(it);
1211+
else
1212+
break;
1213+
}
1214+
}
1215+
#endif
1216+
1217+
//-------------------------------------------------------------------------------------------------
1218+
void WeaponTemplate::trimTriggeredHistoricDamage() const
1219+
{
1220+
HistoricWeaponDamageList::iterator it = m_historicDamage.begin();
1221+
1222+
while (it != m_historicDamage.end())
1223+
{
1224+
if (it->triggerId == m_historicDamageTriggerId)
1225+
it = m_historicDamage.erase(it);
1226+
else
1227+
++it;
1228+
}
1229+
}
11931230

11941231
//-------------------------------------------------------------------------------------------------
11951232
static Bool is2DDistSquaredLessThan(const Coord3D& a, const Coord3D& b, Real distSqr)
@@ -1199,27 +1236,20 @@ static Bool is2DDistSquaredLessThan(const Coord3D& a, const Coord3D& b, Real dis
11991236
}
12001237

12011238
//-------------------------------------------------------------------------------------------------
1202-
void WeaponTemplate::dealDamageInternal(ObjectID sourceID, ObjectID victimID, const Coord3D *pos, const WeaponBonus& bonus, Bool isProjectileDetonation) const
1239+
#if RETAIL_COMPATIBLE_CRC
1240+
void WeaponTemplate::processHistoricDamage(const Object* source, const Coord3D* pos) const
12031241
{
1204-
if (sourceID == 0) // must have a source
1205-
return;
1206-
1207-
if (victimID == 0 && pos == NULL) // must have some sort of destination
1208-
return;
1209-
1210-
Object *source = TheGameLogic->findObjectByID(sourceID); // might be null...
1211-
12121242
//
12131243
/** @todo We need to rewrite the historic stuff ... if you fire 5 missiles, and the 5th,
12141244
// one creates a firestorm ... and then half a second later another volley of 5 missiles
12151245
// come in, the second wave of 5 missiles would all do a historic weapon, making 5 more
12161246
// firestorms (CBD) */
12171247
//
12181248

1219-
trimOldHistoricDamage();
1220-
12211249
if( m_historicBonusCount > 0 && m_historicBonusWeapon != this )
12221250
{
1251+
trimOldHistoricDamage();
1252+
12231253
Real radSqr = m_historicBonusRadius * m_historicBonusRadius;
12241254
Int count = 0;
12251255
UnsignedInt frameNow = TheGameLogic->getFrame();
@@ -1254,6 +1284,58 @@ void WeaponTemplate::dealDamageInternal(ObjectID sourceID, ObjectID victimID, co
12541284
}
12551285

12561286
}
1287+
}
1288+
#else
1289+
void WeaponTemplate::processHistoricDamage(const Object* source, const Coord3D* pos) const
1290+
{
1291+
if (m_historicBonusCount > 0 && m_historicBonusWeapon != this)
1292+
{
1293+
trimOldHistoricDamage();
1294+
1295+
++m_historicDamageTriggerId;
1296+
1297+
const Int requiredCount = m_historicBonusCount - 1; // minus 1 since we include ourselves implicitly
1298+
if (m_historicDamage.size() >= requiredCount)
1299+
{
1300+
const Real radSqr = m_historicBonusRadius * m_historicBonusRadius;
1301+
Int count = 0;
1302+
1303+
for (HistoricWeaponDamageList::iterator it = m_historicDamage.begin(); it != m_historicDamage.end(); ++it)
1304+
{
1305+
if (is2DDistSquaredLessThan(*pos, it->location, radSqr))
1306+
{
1307+
// This one is close enough in time and distance, so count it. This is tracked by template since it applies
1308+
// across units, so don't try to clear historicDamage on success in here.
1309+
it->triggerId = m_historicDamageTriggerId;
1310+
1311+
if (++count == requiredCount)
1312+
{
1313+
TheWeaponStore->createAndFireTempWeapon(m_historicBonusWeapon, source, pos);
1314+
trimTriggeredHistoricDamage();
1315+
return;
1316+
}
1317+
}
1318+
}
1319+
}
1320+
1321+
// add AFTER checking for historic stuff
1322+
m_historicDamage.push_back(HistoricWeaponDamageInfo(TheGameLogic->getFrame(), *pos));
1323+
}
1324+
}
1325+
#endif
1326+
1327+
//-------------------------------------------------------------------------------------------------
1328+
void WeaponTemplate::dealDamageInternal(ObjectID sourceID, ObjectID victimID, const Coord3D *pos, const WeaponBonus& bonus, Bool isProjectileDetonation) const
1329+
{
1330+
if (sourceID == 0) // must have a source
1331+
return;
1332+
1333+
if (victimID == 0 && pos == NULL) // must have some sort of destination
1334+
return;
1335+
1336+
Object *source = TheGameLogic->findObjectByID(sourceID); // might be null...
1337+
1338+
processHistoricDamage(source, pos);
12571339

12581340
//DEBUG_LOG(("WeaponTemplate::dealDamageInternal: dealing damage %s at frame %d",m_name.str(),TheGameLogic->getFrame()));
12591341

0 commit comments

Comments
 (0)