Skip to content

Commit f4439a1

Browse files
NetsuNegiCoronia
andauthored
Weapon target filtering by health percentage (#1450)
``` [SOMEWEAPON] ; WeaponType CanTarget.MaxHealth=1.0 ; double CanTarget.MinHealth=0.0 ; double ``` --------- Co-authored-by: Coronia <[email protected]>
1 parent 72bbf8b commit f4439a1

File tree

9 files changed

+177
-117
lines changed

9 files changed

+177
-117
lines changed

CREDITS.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -404,6 +404,7 @@ This page lists all the individual contributions to the project by their author.
404404
- Fix the bug that uncontrolled scatter when elite techno attacked by aircraft or some unit try crush it
405405
- Exclusive SuperWeapon Sidebar
406406
- Fix the bug that AlphaImage remained after unit entered tunnel.
407+
- Weapon target filtering by health percentage
407408
- **Apollo** - Translucent SHP drawing patches
408409
- **ststl**:
409410
- Customizable `ShowTimer` priority of superweapons

docs/New-or-Enhanced-Logics.md

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2337,7 +2337,9 @@ This function is only used as an additional scattering visual display, which is
23372337

23382338
In `rulesmd.ini`:
23392339
```ini
2340-
[SOMEWEAPON] ; WeaponType
2341-
CanTarget=all ; List of Affected Target Enumeration (none|land|water|empty|infantry|units|buildings|all)
2342-
CanTargetHouses=all ; List of Affected House Enumeration (none|owner/self|allies/ally|team|enemies/enemy|all)
2340+
[SOMEWEAPON] ; WeaponType
2341+
CanTarget=all ; List of Affected Target Enumeration (none|land|water|empty|infantry|units|buildings|all)
2342+
CanTargetHouses=all ; List of Affected House Enumeration (none|owner/self|allies/ally|team|enemies/enemy|all)
2343+
CanTarget.MaxHealth=1.0 ; floating point value, percents or absolute
2344+
CanTarget.MinHealth=0.0 ; floating point value, percents or absolute
23432345
```

docs/Whats-New.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -387,6 +387,7 @@ New:
387387
- [Disable DamageSound for buildings](Fixed-or-Improved-Logics.md#disable-damagesound) (by Otamaa)
388388
- [Power plant damage factor](Fixed-or-Improved-Logics#power-plant-damage-factor) (by Otamaa and Ollerus)
389389
- Customize whether `Crater=yes` animation would destroy tiberium (by TaranDahl)
390+
- Weapon target filtering by health percentage (by NetsuNegi)
390391
391392
Vanilla fixes:
392393
- Fixed sidebar not updating queued unit numbers when adding or removing units when the production is on hold (by CrimRecya)

src/Ext/Bullet/Hooks.DetonateLogics.cpp

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -383,9 +383,13 @@ static bool IsAllowedSplitsTarget(TechnoClass* pSource, HouseClass* pOwner, Weap
383383

384384
auto const pWeaponExt = WeaponTypeExt::ExtMap.Find(pWeapon);
385385

386+
if (pWeaponExt->SkipWeaponPicking)
387+
return true;
388+
386389
if (!EnumFunctions::CanTargetHouse(pWeaponExt->CanTargetHouses, pOwner, pTarget->Owner)
387390
|| !EnumFunctions::IsCellEligible(pTarget->GetCell(), pWeaponExt->CanTarget, true, true)
388-
|| !EnumFunctions::IsTechnoEligible(pTarget, pWeaponExt->CanTarget))
391+
|| !EnumFunctions::IsTechnoEligible(pTarget, pWeaponExt->CanTarget)
392+
|| !pWeaponExt->IsHealthRatioEligible(pTarget))
389393
{
390394
return false;
391395
}

src/Ext/Bullet/Hooks.cpp

Lines changed: 19 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -237,22 +237,32 @@ DEFINE_HOOK(0x46A4FB, BulletClass_Shrapnel_Targeting, 0x6)
237237
{
238238
auto const pWeaponExt = WeaponTypeExt::ExtMap.Find(pShrapnelWeapon);
239239
auto const pType = pObject->GetType();
240-
auto const pWH = pShrapnelWeapon->Warhead;
241-
auto armorType = pType->Armor;
242240

243-
if (!pType->LegalTarget || !EnumFunctions::IsCellEligible(pObject->GetCell(), pWeaponExt->CanTarget, true, true))
241+
if (!pType->LegalTarget)
242+
return SkipObject;
243+
244+
if (!pWeaponExt->SkipWeaponPicking && !EnumFunctions::IsCellEligible(pObject->GetCell(), pWeaponExt->CanTarget, true, true))
244245
return SkipObject;
245246

247+
auto const pWH = pShrapnelWeapon->Warhead;
248+
auto armorType = pType->Armor;
249+
246250
if (auto const pTechno = abstract_cast<TechnoClass*, true>(pObject))
247251
{
248-
if (!EnumFunctions::CanTargetHouse(pWeaponExt->CanTargetHouses, pOwner, pTechno->Owner))
249-
return SkipObject;
252+
if (!pWeaponExt->SkipWeaponPicking)
253+
{
254+
if (!EnumFunctions::CanTargetHouse(pWeaponExt->CanTargetHouses, pOwner, pTechno->Owner))
255+
return SkipObject;
256+
257+
if (!EnumFunctions::IsTechnoEligible(pTechno, pWeaponExt->CanTarget))
258+
return SkipObject;
250259

251-
if (!EnumFunctions::IsTechnoEligible(pTechno, pWeaponExt->CanTarget))
252-
return SkipObject;
260+
if (!pWeaponExt->IsHealthRatioEligible(pTechno))
261+
return SkipObject;
253262

254-
if (!pWeaponExt->HasRequiredAttachedEffects(pTechno, pSource))
255-
return SkipObject;
263+
if (!pWeaponExt->HasRequiredAttachedEffects(pTechno, pSource))
264+
return SkipObject;
265+
}
256266

257267
auto const pShield = TechnoExt::ExtMap.Find(pTechno)->Shield.get();
258268

src/Ext/Techno/Hooks.Firing.cpp

Lines changed: 74 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -51,8 +51,8 @@ DEFINE_HOOK(0x6F33CD, TechnoClass_WhatWeaponShouldIUse_ForceFire, 0x6)
5151
auto const pWeaponSecondary = pThis->GetWeapon(1)->WeaponType;
5252
auto const pPrimaryExt = WeaponTypeExt::ExtMap.Find(pWeaponPrimary);
5353

54-
if (pWeaponSecondary && (!EnumFunctions::IsCellEligible(pCell, pPrimaryExt->CanTarget, true, true)
55-
|| (pPrimaryExt->AttachEffect_CheckOnFirer && !pPrimaryExt->HasRequiredAttachedEffects(pThis, pThis))))
54+
if (pWeaponSecondary && !pPrimaryExt->SkipWeaponPicking && (!EnumFunctions::IsCellEligible(pCell, pPrimaryExt->CanTarget, true, true)
55+
|| !pPrimaryExt->IsHealthRatioEligible(pThis) || (pPrimaryExt->AttachEffect_CheckOnFirer && !pPrimaryExt->HasRequiredAttachedEffects(pThis, pThis))))
5656
{
5757
R->EAX(1);
5858
return ReturnWeaponIndex;
@@ -83,6 +83,7 @@ DEFINE_HOOK(0x6F3428, TechnoClass_WhatWeaponShouldIUse_ForceWeapon, 0x6)
8383

8484
// Force weapon
8585
const int forceWeaponIndex = pTypeExt->SelectForceWeapon(pThis, pTarget);
86+
8687
if (forceWeaponIndex >= 0)
8788
{
8889
R->EAX(forceWeaponIndex);
@@ -101,9 +102,9 @@ DEFINE_HOOK(0x6F36DB, TechnoClass_WhatWeaponShouldIUse, 0x8)
101102

102103
const auto pTargetTechno = abstract_cast<TechnoClass*>(pTarget);
103104
const auto pTypeExt = TechnoTypeExt::ExtMap.Find(pThis->GetTechnoType());
104-
bool allowFallback = !pTypeExt->NoSecondaryWeaponFallback;
105-
bool allowAAFallback = allowFallback ? true : pTypeExt->NoSecondaryWeaponFallback_AllowAA;
106-
int weaponIndex = TechnoExt::PickWeaponIndex(pThis, pTargetTechno, pTarget, 0, 1, allowFallback, allowAAFallback);
105+
const bool allowFallback = !pTypeExt->NoSecondaryWeaponFallback;
106+
const bool allowAAFallback = allowFallback ? true : pTypeExt->NoSecondaryWeaponFallback_AllowAA;
107+
const int weaponIndex = TechnoExt::PickWeaponIndex(pThis, pTargetTechno, pTarget, 0, 1, allowFallback, allowAAFallback);
107108

108109
if (weaponIndex != -1)
109110
return weaponIndex == 1 ? Secondary : Primary;
@@ -117,8 +118,8 @@ DEFINE_HOOK(0x6F36DB, TechnoClass_WhatWeaponShouldIUse, 0x8)
117118
{
118119
if (pShield->IsActive())
119120
{
120-
auto const secondary = pThis->GetWeapon(1)->WeaponType;
121-
bool secondaryIsAA = pTargetTechno && pTargetTechno->IsInAir() && secondary && secondary->Projectile->AA;
121+
const auto secondary = pThis->GetWeapon(1)->WeaponType;
122+
const bool secondaryIsAA = pTargetTechno && pTargetTechno->IsInAir() && secondary && secondary->Projectile->AA;
122123

123124
if (secondary && (allowFallback || (allowAAFallback && secondaryIsAA) || TechnoExt::CanFireNoAmmoWeapon(pThis, 1)))
124125
{
@@ -162,10 +163,10 @@ DEFINE_HOOK(0x6F3432, TechnoClass_WhatWeaponShouldIUse_Gattling, 0xA)
162163
GET_STACK(AbstractClass*, pTarget, STACK_OFFSET(0x18, 0x4));
163164

164165
auto const pTargetTechno = abstract_cast<TechnoClass*>(pTarget);
165-
int oddWeaponIndex = 2 * pThis->CurrentGattlingStage;
166-
int evenWeaponIndex = oddWeaponIndex + 1;
166+
const int oddWeaponIndex = 2 * pThis->CurrentGattlingStage;
167+
const int evenWeaponIndex = oddWeaponIndex + 1;
167168
int chosenWeaponIndex = oddWeaponIndex;
168-
int eligibleWeaponIndex = TechnoExt::PickWeaponIndex(pThis, pTargetTechno, pTarget, oddWeaponIndex, evenWeaponIndex, true);
169+
const int eligibleWeaponIndex = TechnoExt::PickWeaponIndex(pThis, pTargetTechno, pTarget, oddWeaponIndex, evenWeaponIndex, true);
169170

170171
if (eligibleWeaponIndex != -1)
171172
{
@@ -196,7 +197,7 @@ DEFINE_HOOK(0x6F3432, TechnoClass_WhatWeaponShouldIUse_Gattling, 0xA)
196197
else
197198
{
198199
auto const landType = pTargetTechno->GetCell()->LandType;
199-
bool isOnWater = (landType == LandType::Water || landType == LandType::Beach) && !pTargetTechno->IsInAir();
200+
const bool isOnWater = (landType == LandType::Water || landType == LandType::Beach) && !pTargetTechno->IsInAir();
200201

201202
if (!pTargetTechno->OnBridge && isOnWater)
202203
{
@@ -255,59 +256,62 @@ DEFINE_HOOK(0x6FC339, TechnoClass_CanFire, 0x6)
255256
return CannotFire;
256257
}
257258

258-
if (const auto pWeaponExt = WeaponTypeExt::ExtMap.Find(pWeapon))
259+
// AAOnly doesn't need to be checked if LandTargeting=1.
260+
if (pThis->GetTechnoType()->LandTargeting != LandTargetingType::Land_Not_OK && pWeapon->Projectile->AA && pTarget && !pTarget->IsInAir())
259261
{
260-
const auto pTechno = abstract_cast<TechnoClass*>(pTarget);
261-
CellClass* pTargetCell = nullptr;
262+
const auto pBulletTypeExt = BulletTypeExt::ExtMap.Find(pWeapon->Projectile);
262263

263-
// AAOnly doesn't need to be checked if LandTargeting=1.
264-
if (pThis->GetTechnoType()->LandTargeting != LandTargetingType::Land_Not_OK && pWeapon->Projectile->AA && pTarget && !pTarget->IsInAir())
265-
{
266-
auto const pBulletTypeExt = BulletTypeExt::ExtMap.Find(pWeapon->Projectile);
264+
if (pBulletTypeExt->AAOnly)
265+
return CannotFire;
266+
}
267267

268-
if (pBulletTypeExt->AAOnly)
269-
return CannotFire;
270-
}
268+
const auto pTechno = abstract_cast<TechnoClass*>(pTarget);
269+
CellClass* pTargetCell = nullptr;
271270

272-
if (pTarget)
271+
if (pTarget)
272+
{
273+
if (const auto pCell = abstract_cast<CellClass*>(pTarget))
273274
{
274-
if (const auto pCell = abstract_cast<CellClass*>(pTarget))
275-
{
276-
pTargetCell = pCell;
277-
}
278-
else if (const auto pObject = abstract_cast<ObjectClass*>(pTarget))
279-
{
280-
// Ignore target cell for technos that are in air.
281-
if ((pTechno && !pTechno->IsInAir()) || pObject != pTechno)
282-
pTargetCell = pObject->GetCell();
283-
}
275+
pTargetCell = pCell;
284276
}
285-
286-
if (pTargetCell)
277+
else if (const auto pObject = abstract_cast<ObjectClass*>(pTarget))
287278
{
288-
if (!EnumFunctions::IsCellEligible(pTargetCell, pWeaponExt->CanTarget, true, true))
289-
return CannotFire;
279+
// Ignore target cell for technos that are in air.
280+
if ((pTechno && !pTechno->IsInAir()) || pObject != pTechno)
281+
pTargetCell = pObject->GetCell();
290282
}
283+
}
291284

292-
if (pTechno)
285+
const auto pWeaponExt = WeaponTypeExt::ExtMap.Find(pWeapon);
286+
287+
if (!pWeaponExt->SkipWeaponPicking && pTargetCell)
288+
{
289+
if (!EnumFunctions::IsCellEligible(pTargetCell, pWeaponExt->CanTarget, true, true))
290+
return CannotFire;
291+
}
292+
293+
if (pTechno)
294+
{
295+
if (!pWeaponExt->SkipWeaponPicking)
293296
{
294297
if (!EnumFunctions::IsTechnoEligible(pTechno, pWeaponExt->CanTarget) ||
295-
!EnumFunctions::CanTargetHouse(pWeaponExt->CanTargetHouses, pThis->Owner, pTechno->Owner))
298+
!EnumFunctions::CanTargetHouse(pWeaponExt->CanTargetHouses, pThis->Owner, pTechno->Owner) ||
299+
!pWeaponExt->IsHealthRatioEligible(pTechno))
296300
{
297301
return CannotFire;
298302
}
299303

300304
if (!pWeaponExt->HasRequiredAttachedEffects(pTechno, pThis))
301305
return CannotFire;
306+
}
302307

303-
if (pWH->Airstrike)
304-
{
305-
if (!pWHExt || !EnumFunctions::IsTechnoEligible(pTechno, pWHExt->AirstrikeTargets))
306-
return CannotFire;
308+
if (pWH->Airstrike)
309+
{
310+
if (!pWHExt || !EnumFunctions::IsTechnoEligible(pTechno, pWHExt->AirstrikeTargets))
311+
return CannotFire;
307312

308-
if (!TechnoTypeExt::ExtMap.Find(pTechno->GetTechnoType())->AllowAirstrike.Get(pTechno->AbstractFlags & AbstractFlags::Foot ? true : static_cast<BuildingClass*>(pTechno)->Type->CanC4))
309-
return CannotFire;
310-
}
313+
if (!TechnoTypeExt::ExtMap.Find(pTechno->GetTechnoType())->AllowAirstrike.Get(pTechno->AbstractFlags & AbstractFlags::Foot ? true : static_cast<BuildingClass*>(pTechno)->Type->CanC4))
314+
return CannotFire;
311315
}
312316
}
313317

@@ -499,22 +503,29 @@ DEFINE_HOOK(0x6FE19A, TechnoClass_FireAt_AreaFire, 0x6)
499503

500504
if (auto pExt = WeaponTypeExt::ExtMap.Find(pWeaponType))
501505
{
506+
const auto canTarget = pExt->CanTarget;
507+
const auto canTargetHouses = pExt->CanTargetHouses;
508+
const bool skipWeaponPicking = pExt->SkipWeaponPicking;
509+
const bool onBridge = pThis->OnBridge;
510+
const int level = pThis->GetCell()->Level;
511+
502512
if (pExt->AreaFire_Target == AreaFireTarget::Random)
503513
{
504-
auto const range = WeaponTypeExt::GetRangeWithModifiers(pWeaponType, pThis) / static_cast<double>(Unsorted::LeptonsPerCell);
505-
514+
const auto range = WeaponTypeExt::GetRangeWithModifiers(pWeaponType, pThis) / static_cast<double>(Unsorted::LeptonsPerCell);
515+
const auto pOwner = pThis->Owner;
516+
const auto mapCoords = pCell->MapCoords;
506517
std::vector<CellStruct> adjacentCells = GeneralUtils::AdjacentCellsInRange(static_cast<size_t>(range + 0.99));
507-
size_t size = adjacentCells.size();
518+
const size_t size = adjacentCells.size();
508519

509520
for (unsigned int i = 0; i < size; i++)
510521
{
511522
int rand = ScenarioClass::Instance->Random.RandomRanged(0, size - 1);
512523
unsigned int cellIndex = (i + rand) % size;
513-
CellStruct tgtPos = pCell->MapCoords + adjacentCells[cellIndex];
524+
CellStruct tgtPos = mapCoords + adjacentCells[cellIndex];
514525
CellClass* tgtCell = MapClass::Instance.TryGetCellAt(tgtPos);
515-
bool allowBridges = tgtCell && tgtCell->ContainsBridge() && (pThis->OnBridge || tgtCell->Level + CellClass::BridgeLevels == pThis->GetCell()->Level);
526+
bool allowBridges = tgtCell && tgtCell->ContainsBridge() && (onBridge || tgtCell->Level + CellClass::BridgeLevels == level);
516527

517-
if (EnumFunctions::AreCellAndObjectsEligible(tgtCell, pExt->CanTarget, pExt->CanTargetHouses, pThis->Owner, true, false, allowBridges))
528+
if (skipWeaponPicking || EnumFunctions::AreCellAndObjectsEligible(tgtCell, canTarget, canTargetHouses, pOwner, true, false, allowBridges))
518529
{
519530
R->EAX(tgtCell);
520531
return 0;
@@ -525,16 +536,16 @@ DEFINE_HOOK(0x6FE19A, TechnoClass_FireAt_AreaFire, 0x6)
525536
}
526537
else if (pExt->AreaFire_Target == AreaFireTarget::Self)
527538
{
528-
if (!EnumFunctions::AreCellAndObjectsEligible(pThis->GetCell(), pExt->CanTarget, pExt->CanTargetHouses, nullptr, false, false, pThis->OnBridge))
539+
if (!skipWeaponPicking && !EnumFunctions::AreCellAndObjectsEligible(pThis->GetCell(), canTarget, canTargetHouses, nullptr, false, false, pThis->OnBridge))
529540
return DoNotFire;
530541

531542
R->EAX(pThis);
532543
return SkipSetTarget;
533544
}
534545

535-
bool allowBridges = pCell && pCell->ContainsBridge() && (pThis->OnBridge || pCell->Level + CellClass::BridgeLevels == pThis->GetCell()->Level);
546+
bool allowBridges = pCell->ContainsBridge() && (onBridge || pCell->Level + CellClass::BridgeLevels == level);
536547

537-
if (!EnumFunctions::AreCellAndObjectsEligible(pCell, pExt->CanTarget, pExt->CanTargetHouses, nullptr, false, false, allowBridges))
548+
if (!skipWeaponPicking && !EnumFunctions::AreCellAndObjectsEligible(pCell, canTarget, canTargetHouses, nullptr, false, false, allowBridges))
538549
return DoNotFire;
539550
}
540551

@@ -625,9 +636,9 @@ static inline void SetChargeTurretDelay(TechnoClass* pThis, int rearmDelay, Weap
625636

626637
if (pWeaponExt->ChargeTurret_Delays.size() > 0)
627638
{
628-
size_t burstIndex = pWeapon->Burst > 1 ? pThis->CurrentBurstIndex - 1 : 0;
629-
size_t index = burstIndex < pWeaponExt->ChargeTurret_Delays.size() ? burstIndex : pWeaponExt->ChargeTurret_Delays.size() - 1;
630-
int delay = pWeaponExt->ChargeTurret_Delays[index];
639+
const size_t burstIndex = pWeapon->Burst > 1 ? pThis->CurrentBurstIndex - 1 : 0;
640+
const size_t index = burstIndex < pWeaponExt->ChargeTurret_Delays.size() ? burstIndex : pWeaponExt->ChargeTurret_Delays.size() - 1;
641+
const int delay = pWeaponExt->ChargeTurret_Delays[index];
631642

632643
if (delay <= 0)
633644
return;
@@ -769,8 +780,8 @@ DEFINE_HOOK(0x6FD0B5, TechnoClass_RearmDelay_ROF, 0x6)
769780

770781
auto const pWeaponExt = WeaponTypeExt::ExtMap.Find(pWeapon);
771782
auto const pExt = TechnoExt::ExtMap.Find(pThis);
772-
auto range = pWeaponExt->ROF_RandomDelay.Get(RulesExt::Global()->ROF_RandomDelay);
773-
double rof = pWeapon->ROF * pExt->AE.ROFMultiplier;
783+
auto const range = pWeaponExt->ROF_RandomDelay.Get(RulesExt::Global()->ROF_RandomDelay);
784+
const double rof = pWeapon->ROF * pExt->AE.ROFMultiplier;
774785
pExt->LastRearmWasFullDelay = true;
775786

776787
R->EAX(GeneralUtils::GetRangedRandomOrSingleValue(range));
@@ -803,7 +814,7 @@ DEFINE_HOOK(0x6FD05E, TechnoClass_RearmDelay_BurstDelays, 0x7)
803814
GET(int, idxCurrentBurst, ECX);
804815

805816
const auto pWeaponExt = WeaponTypeExt::ExtMap.Find(pWeapon);
806-
int burstDelay = pWeaponExt->GetBurstDelay(pThis->CurrentBurstIndex);
817+
const int burstDelay = pWeaponExt->GetBurstDelay(pThis->CurrentBurstIndex);
807818

808819
if (burstDelay >= 0)
809820
{
@@ -848,7 +859,7 @@ DEFINE_HOOK(0x5209AF, InfantryClass_FiringAI_BurstDelays, 0x6)
848859

849860
int cumulativeDelay = 0;
850861
int projectedDelay = 0;
851-
int weaponIndex = FiringAITemp::weaponIndex;
862+
const int weaponIndex = FiringAITemp::weaponIndex;
852863
auto const pWeapon = pThis->GetWeapon(weaponIndex)->WeaponType;
853864
auto const pWeaponExt = WeaponTypeExt::ExtMap.Find(pWeapon);
854865

@@ -857,7 +868,7 @@ DEFINE_HOOK(0x5209AF, InfantryClass_FiringAI_BurstDelays, 0x6)
857868
{
858869
for (int i = 0; i <= pThis->CurrentBurstIndex; i++)
859870
{
860-
int burstDelay = pWeaponExt->GetBurstDelay(i);
871+
const int burstDelay = pWeaponExt->GetBurstDelay(i);
861872
int delay = 0;
862873

863874
if (burstDelay > -1)
@@ -880,7 +891,7 @@ DEFINE_HOOK(0x5209AF, InfantryClass_FiringAI_BurstDelays, 0x6)
880891
{
881892
if (pWeaponExt && pWeaponExt->Burst_FireWithinSequence)
882893
{
883-
int frameCount = pThis->Type->Sequence->GetSequence(pThis->SequenceAnim).CountFrames;
894+
const int frameCount = pThis->Type->Sequence->GetSequence(pThis->SequenceAnim).CountFrames;
884895

885896
// If projected frame for firing next shot goes beyond the sequence frame count, cease firing after this shot and start rearm timer.
886897
if (firingFrame + projectedDelay > frameCount)

0 commit comments

Comments
 (0)