|
14 | 14 | using RotationSolver.Basic.Configuration.Conditions; |
15 | 15 | using RotationSolver.Basic.Rotations.Duties; |
16 | 16 | using System.Collections.Concurrent; |
| 17 | +using System.Collections.Frozen; |
17 | 18 | using Action = Lumina.Excel.Sheets.Action; |
18 | 19 | using CharacterManager = FFXIVClientStructs.FFXIV.Client.Game.Character.CharacterManager; |
19 | 20 | using CombatRole = RotationSolver.Basic.Data.CombatRole; |
@@ -1244,9 +1245,44 @@ public static bool IsPhysicalDamageIncoming() |
1244 | 1245 | return false; |
1245 | 1246 | } |
1246 | 1247 |
|
| 1248 | + // Cached, case-insensitive path sets modeled after WrathCombo VFX.cs |
| 1249 | + private static readonly FrozenSet<string> TankbusterPaths = FrozenSet.ToFrozenSet( |
| 1250 | + [ |
| 1251 | + "vfx/lockon/eff/tank", // Generic TB check |
| 1252 | + "vfx/lockon/eff/x6fe_fan100_50_0t1", // Necron Blue Shockwave - Cone Tankbuster |
| 1253 | + "vfx/common/eff/mon_eisyo03t", // M10 Deep Impact AoE TB (also generic?) |
| 1254 | + "vfx/lockon/eff/m0676trg_tw_d0t1p", // M10 Hot Impact shared TB |
| 1255 | + "vfx/lockon/eff/m0676trg_tw_s6_d0t1p", // M11 Raw Steel |
| 1256 | + "vfx/lockon/eff/z6r2b3_8sec_lockon_c0a1",// Kam'lanaut Princely Blow |
| 1257 | + "vfx/lockon/eff/m0742trg_b1t1", // M7 Abominable Blink |
| 1258 | + "vfx/lockon/eff/x6r9_tank_lockonae" // M9 Hardcore Large TB |
| 1259 | + ], StringComparer.OrdinalIgnoreCase); |
| 1260 | + |
| 1261 | + private static readonly FrozenSet<string> MultiHitSharedPaths = FrozenSet.ToFrozenSet( |
| 1262 | + [ |
| 1263 | + "vfx/lockon/eff/com_share4a1", |
| 1264 | + "vfx/lockon/eff/com_share5a1", |
| 1265 | + "vfx/lockon/eff/com_share6m7s_1v", |
| 1266 | + "vfx/lockon/eff/com_share8s_0v", |
| 1267 | + "vfx/lockon/eff/share_laser_5s_c0w", // Line |
| 1268 | + "vfx/lockon/eff/share_laser_8s_c0g", // Line |
| 1269 | + "vfx/lockon/eff/m0922trg_t2w" |
| 1270 | + ], StringComparer.OrdinalIgnoreCase); |
| 1271 | + |
| 1272 | + private static readonly FrozenSet<string> SharedDamagePaths = FrozenSet.ToFrozenSet( |
| 1273 | + [ |
| 1274 | + "vfx/lockon/eff/coshare", |
| 1275 | + "vfx/lockon/eff/share_laser", |
| 1276 | + "vfx/lockon/eff/com_share", |
| 1277 | + // Duty-specific AOE share markers |
| 1278 | + "vfx/monster/gimmick2/eff/z3o7_b1_g06c0t", // Puppet's Bunker, Superior Flight Unit. |
| 1279 | + "vfx/monster/gimmick4/eff/z5r1_b4_g09c0c" // Aglaia, Nald'thal |
| 1280 | + ], StringComparer.OrdinalIgnoreCase); |
| 1281 | + |
| 1282 | + private static readonly StringComparison PathCmp = StringComparison.OrdinalIgnoreCase; |
1247 | 1283 |
|
1248 | 1284 | public static bool IsHostileCastingAOE => |
1249 | | - InCombat && (IsCastingAreaVfx() || (AllHostileTargets != null && IsAnyHostileCastingArea())); |
| 1285 | + InCombat && (IsCastingAreaVfx() || (AllHostileTargets != null && IsAnyHostileCastingArea())); |
1250 | 1286 |
|
1251 | 1287 | private static bool IsAnyHostileCastingArea() |
1252 | 1288 | { |
@@ -1392,64 +1428,99 @@ public static bool IsCastingVfx(ConcurrentQueue<VfxNewData> vfxData, Func<VfxNew |
1392 | 1428 | return false; |
1393 | 1429 | } |
1394 | 1430 |
|
| 1431 | + // Improved multi-hit detection using a cached set of known multi-hit share paths |
1395 | 1432 | public static bool IsCastingMultiHit() |
1396 | 1433 | { |
1397 | 1434 | return IsCastingVfx(VfxDataQueue, s => |
1398 | 1435 | { |
1399 | | - if (!Player.Available) |
| 1436 | + if (!Player.Available || Player.Object == null) |
1400 | 1437 | { |
1401 | 1438 | return false; |
1402 | 1439 | } |
1403 | 1440 |
|
1404 | | - // For x6fe, ignore target and player role checks. |
1405 | | - if (s.Path.StartsWith("vfx/lockon/eff/com_share5a1")) |
| 1441 | + if (string.IsNullOrEmpty(s.Path)) |
1406 | 1442 | { |
1407 | | - return true; |
| 1443 | + return false; |
1408 | 1444 | } |
1409 | 1445 |
|
1410 | | - if (s.Path.StartsWith("vfx/lockon/eff/m0922trg_t2w")) |
| 1446 | + // Any path in multi-hit share list qualifies |
| 1447 | + foreach (var p in MultiHitSharedPaths) |
1411 | 1448 | { |
1412 | | - return true; |
| 1449 | + if (s.Path.StartsWith(p, PathCmp)) |
| 1450 | + { |
| 1451 | + return true; |
| 1452 | + } |
1413 | 1453 | } |
1414 | 1454 |
|
1415 | 1455 | return false; |
1416 | 1456 | }); |
1417 | 1457 | } |
1418 | 1458 |
|
| 1459 | + // Improved tank VFX detection: recognizes generic and specific TB paths, plus preserves original tank lock-on checks |
1419 | 1460 | public static bool IsCastingTankVfx() |
1420 | 1461 | { |
1421 | 1462 | return IsCastingVfx(VfxDataQueue, s => |
1422 | 1463 | { |
1423 | | - if (!Player.Available) |
| 1464 | + if (!Player.Available || Player.Object == null) |
1424 | 1465 | { |
1425 | 1466 | return false; |
1426 | 1467 | } |
1427 | 1468 |
|
1428 | | - if (Player.Object == null) |
| 1469 | + if (string.IsNullOrEmpty(s.Path)) |
1429 | 1470 | { |
1430 | 1471 | return false; |
1431 | 1472 | } |
1432 | 1473 |
|
1433 | | - // For x6fe, ignore target and player role checks. |
1434 | | - if (s.Path.StartsWith("vfx/lockon/eff/x6fe")) |
| 1474 | + // Specific tankbuster effect paths (priority) |
| 1475 | + foreach (var p in TankbusterPaths) |
1435 | 1476 | { |
1436 | | - return true; |
| 1477 | + if (s.Path.StartsWith(p, PathCmp)) |
| 1478 | + { |
| 1479 | + return true; |
| 1480 | + } |
1437 | 1481 | } |
1438 | 1482 |
|
1439 | 1483 | // Preserve original checks for other tank lock-on effects. |
1440 | 1484 | return (!TargetFilter.PlayerJobCategory(JobRole.Tank) || s.ObjectId == Player.Object.GameObjectId) |
1441 | | - && (s.Path.StartsWith("vfx/lockon/eff/tank_lockon") |
1442 | | - || s.Path.StartsWith("vfx/lockon/eff/tank_laser")); |
| 1485 | + && (s.Path.StartsWith("vfx/lockon/eff/tank_lockon", PathCmp) |
| 1486 | + || s.Path.StartsWith("vfx/lockon/eff/tank_laser", PathCmp)); |
1443 | 1487 | }); |
1444 | 1488 | } |
1445 | 1489 |
|
| 1490 | + // Improved shared AOE detection using cached sets, covers both regular and multi-hit stack markers |
1446 | 1491 | public static bool IsCastingAreaVfx() |
1447 | 1492 | { |
1448 | 1493 | return IsCastingVfx(VfxDataQueue, s => |
1449 | 1494 | { |
1450 | | - return Player.Available && (s.Path.StartsWith("vfx/lockon/eff/coshare") |
1451 | | - || s.Path.StartsWith("vfx/lockon/eff/share_laser") |
1452 | | - || s.Path.StartsWith("vfx/lockon/eff/com_share")); |
| 1495 | + if (!Player.Available || Player.Object == null) |
| 1496 | + { |
| 1497 | + return false; |
| 1498 | + } |
| 1499 | + |
| 1500 | + if (string.IsNullOrEmpty(s.Path)) |
| 1501 | + { |
| 1502 | + return false; |
| 1503 | + } |
| 1504 | + |
| 1505 | + // Multi-hit markers (treated as area/stack mechanics) |
| 1506 | + foreach (var p in MultiHitSharedPaths) |
| 1507 | + { |
| 1508 | + if (s.Path.StartsWith(p, PathCmp)) |
| 1509 | + { |
| 1510 | + return true; |
| 1511 | + } |
| 1512 | + } |
| 1513 | + |
| 1514 | + // Regular shared markers |
| 1515 | + foreach (var p in SharedDamagePaths) |
| 1516 | + { |
| 1517 | + if (s.Path.StartsWith(p, PathCmp)) |
| 1518 | + { |
| 1519 | + return true; |
| 1520 | + } |
| 1521 | + } |
| 1522 | + |
| 1523 | + return false; |
1453 | 1524 | }); |
1454 | 1525 | } |
1455 | 1526 |
|
|
0 commit comments