triplea-game / triplea

TripleA is a turn based strategy game and board game engine, similar to Axis & Allies or Risk.
https://triplea-game.org/
GNU General Public License v3.0
1.33k stars 392 forks source link

2.0.19404: AaCasualtySelector#individuallyFiredAaCasualties:369 - IndexOutOfBoundsException #6496

Closed tripleabuilderbot closed 2 years ago

tripleabuilderbot commented 4 years ago

User Description

This crashing error happened in on the Warcraft: War Heroes map in round 11. 4 AI vs 4 of me/human. It happened as AI made or calculated NCM. //FrostionAAA

Log Message

Index 2 out of bounds for length 2

TripleA Version

2.0.19404

Java Version

11.0.6

Operating System

Windows 10

Exception

java.lang.IndexOutOfBoundsException: Index 2 out of bounds for length 2

Stack Trace

java.lang.IndexOutOfBoundsException: Index 2 out of bounds for length 2
    at java.base/jdk.internal.util.Preconditions.outOfBounds(Preconditions.java:64)
    at java.base/jdk.internal.util.Preconditions.outOfBoundsCheckIndex(Preconditions.java:70)
    at java.base/jdk.internal.util.Preconditions.checkIndex(Preconditions.java:248)
    at java.base/java.util.Objects.checkIndex(Objects.java:372)
    at java.base/java.util.ArrayList.get(ArrayList.java:458)
    at games.strategy.triplea.delegate.battle.casualty.AaCasualtySelector.individuallyFiredAaCasualties(AaCasualtySelector.java:369)
    at games.strategy.triplea.delegate.battle.casualty.AaCasualtySelector.getAaCasualties(AaCasualtySelector.java:93)
    at games.strategy.triplea.delegate.battle.FireAa.selectCasualties(FireAa.java:191)
    at games.strategy.triplea.delegate.battle.FireAa$2.execute(FireAa.java:146)
    at games.strategy.triplea.delegate.ExecutionStack.execute(ExecutionStack.java:34)
    at games.strategy.triplea.delegate.battle.MustFightBattle.fight(MustFightBattle.java:649)
    at games.strategy.triplea.odds.calculator.BattleCalculator.calculate(BattleCalculator.java:114)
    at games.strategy.triplea.odds.calculator.ConcurrentBattleCalculator.lambda$calculate$4(ConcurrentBattleCalculator.java:222)
    at java.base/java.util.stream.ReferencePipeline$3$1.accept(ReferencePipeline.java:195)
    at java.base/java.util.Spliterators$ArraySpliterator.forEachRemaining(Spliterators.java:948)
    at java.base/java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:484)
    at java.base/java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:474)
    at java.base/java.util.stream.ReduceOps$ReduceTask.doLeaf(ReduceOps.java:952)
    at java.base/java.util.stream.ReduceOps$ReduceTask.doLeaf(ReduceOps.java:926)
    at java.base/java.util.stream.AbstractTask.compute(AbstractTask.java:327)
    at java.base/java.util.concurrent.CountedCompleter.exec(CountedCompleter.java:746)
    at java.base/java.util.concurrent.ForkJoinTask.doExec(ForkJoinTask.java:290)
    at java.base/java.util.concurrent.ForkJoinTask.doInvoke(ForkJoinTask.java:408)
    at java.base/java.util.concurrent.ForkJoinTask.invoke(ForkJoinTask.java:736)
    at java.base/java.util.stream.ReduceOps$ReduceOp.evaluateParallel(ReduceOps.java:919)
    at java.base/java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:233)
    at java.base/java.util.stream.ReferencePipeline.collect(ReferencePipeline.java:578)
    at games.strategy.triplea.odds.calculator.ConcurrentBattleCalculator.calculate(ConcurrentBattleCalculator.java:234)
    at games.strategy.triplea.ai.pro.util.ProOddsCalculator.callBattleCalc(ProOddsCalculator.java:224)
    at games.strategy.triplea.ai.pro.util.ProOddsCalculator.callBattleCalc(ProOddsCalculator.java:200)
    at games.strategy.triplea.ai.pro.util.ProOddsCalculator.calculateBattleResults(ProOddsCalculator.java:133)
    at games.strategy.triplea.ai.pro.util.ProOddsCalculator.calculateBattleResults(ProOddsCalculator.java:116)
    at games.strategy.triplea.ai.pro.ProNonCombatMoveAi.determineIfMoveTerritoriesCanBeHeld(ProNonCombatMoveAi.java:455)
    at games.strategy.triplea.ai.pro.ProNonCombatMoveAi.doNonCombatMove(ProNonCombatMoveAi.java:98)
    at games.strategy.triplea.ai.pro.AbstractProAi.move(AbstractProAi.java:135)
    at games.strategy.triplea.ai.AbstractAi.start(AbstractAi.java:514)
    at games.strategy.engine.framework.ServerGame.waitForPlayerToFinishStep(ServerGame.java:538)
    at games.strategy.engine.framework.ServerGame.runStep(ServerGame.java:408)
    at games.strategy.engine.framework.ServerGame.startGame(ServerGame.java:297)
    at games.strategy.engine.framework.startup.launcher.LocalLauncher.launchInternal(LocalLauncher.java:61)
    at games.strategy.engine.framework.startup.launcher.LocalLauncher.launchInternal(LocalLauncher.java:34)
    at games.strategy.engine.framework.startup.launcher.AbstractLauncher.lambda$launch$0(AbstractLauncher.java:16)
    at java.base/java.lang.Thread.run(Thread.java:834)
asvitkine commented 4 years ago

Crash is on this line:

          final Unit unit = planesList.get(i);

In context:

      final List<Die> rolls = dice.getRolls(highestAttack);
      for (int i = 0; i < rolls.size(); i++) {
        final Die die = rolls.get(i);
        if (die.getType() == DieType.HIT) {
          final Unit unit = planesList.get(i);

Seems like there's a clear logic error? I'm not sure why it's using index i (which is for dice rolls), to index into the planes list.

asvitkine commented 4 years ago

Also seems related to the error: https://github.com/triplea-game/triplea/issues/6485

asvitkine commented 4 years ago

I guess the logic "works" if there's always the same or smaller number of AA dice rolls as there are planes.

So if AA guns don't stack or don't roll multiple dice.

So I guess the question is what the set up in this map is like for AA.

@FrostionAAA can you clarify?

FrostionAAA commented 4 years ago

@asvitkine No. I dont know what to answer. There are many AA unit in the map and they are very different. Here is a copy of the XML.

DanVanAtta commented 4 years ago

@FrostionAAA it seems that you're able to hit this problem late game. Can you make any guesses which AA units might be involved? The suspected unit is something that is probably unlocked later in the game, and it is going to be something that the AI is considered attacking or being attacked by, so a unit that is newly on a border shared by an AI.

In theory we could potentially recreate this problem by running battle calculator with those units. If we can reliably recreate the problem we should be able to get it fixed.

asvitkine commented 4 years ago

If you hit the error, could you quit the game and upload a recent autosave before it? We might be able to repro with that and fix the problem.

asvitkine commented 4 years ago

Looking at the code more, I believe this code is meant to ensure there's fewer rolls than targets:

  /**
   * Finds total number of AA attacks that a group of units can roll against targets taking into
   * account infinite roll and overstack AA.
   */
  public static int getTotalAaAttacks(
      final Map<Unit, TotalPowerAndTotalRolls> unitPowerAndRollsMap,
      final Collection<Unit> validTargets) {
    if (unitPowerAndRollsMap.isEmpty() || validTargets.isEmpty()) {
      return 0;
    }
    int totalAAattacksNormal = 0;
    int totalAAattacksSurplus = 0;
    for (final Entry<Unit, TotalPowerAndTotalRolls> entry : unitPowerAndRollsMap.entrySet()) {
      if (entry.getValue().getTotalPower() == 0 || entry.getValue().getTotalRolls() == 0) {
        continue;
      }
      final UnitAttachment ua = UnitAttachment.get(entry.getKey().getType());
      if (entry.getValue().getTotalRolls() == -1) {
        totalAAattacksNormal = validTargets.size();
      } else {
        if (ua.getMayOverStackAa()) {
          totalAAattacksSurplus += entry.getValue().getTotalRolls();
        } else {
          totalAAattacksNormal += entry.getValue().getTotalRolls();
        }
      }
    }
    totalAAattacksNormal = Math.min(totalAAattacksNormal, validTargets.size());
    return totalAAattacksNormal + totalAAattacksSurplus;
  }

However, the Math.min() call is not on the final result, but on totalAAattacksNormal. So if totalAAattacksSurplus is set, there can be more dice rolled than targets.

But we'd need to have a repro to confirm that's what's actually happening.

asvitkine commented 4 years ago

And looking at the XML, it does look like there are a bunch of units marked with <option name="mayOverStackAA" value="true"/>.

asvitkine commented 4 years ago

Here's a search for other maps that may use that option:

https://github.com/search?l=XML&q=mayOverStackAA&type=Code

trevan commented 4 years ago

This will probably be fixed with #6831. The AAroll and AAstrength support were being calculated during the casualty selection with the wrong units (the attacked units were supporting the attacker units and vice versa).

stale[bot] commented 4 years ago

This issue has been automatically marked as stale because it has not had recent activity. If there is something that can be done to resolve this issue, please add a comment indicating what that would be and this issue will be re-opened. If there are multiple items that can be completed independently, we encourage you to use the "reference in new issue" option next to any outstanding comment so that we may divide and conquer.

asvitkine commented 2 years ago

Since there are no other bugs filed for this for recent versions, I assume this is fixed per comments above. Closing.