Skip to content

Commit

Permalink
Merge pull request #244 from vincent4vx/fix-ai-pathfinding-failed
Browse files Browse the repository at this point in the history
close #94 [AI] Do not crash on movement near enemy on inaccessible cell
  • Loading branch information
vincent4vx authored Apr 18, 2022
2 parents 6db1994 + bb907c1 commit 90cf7fc
Show file tree
Hide file tree
Showing 6 changed files with 64 additions and 4 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ public void initialize(AI<F> ai) {
this.helper = ai.helper();
this.pathfinder = helper.cells().pathfinder()
.targetDistance(1)
.walkablePredicate(FightCell::walkableIgnoreFighter)
.walkablePredicate(cell -> true) // Fix #94 Ignore inaccessible cell (handled by cell cost)
.cellWeightFunction(this::cellCost)
;
}
Expand All @@ -57,9 +57,11 @@ public Optional<Action> generate(AI<F> ai, ActionsFactory<F> actions) {
}

final int movementPoints = helper.movementPoints();
final FightCell currentCell = ai.fighter().cell();

return ai.enemy()
.map(enemy -> NullnessUtil.castNonNull(pathfinder).findPath(ai.fighter().cell(), enemy.cell()).truncate(movementPoints + 1))
.map(enemy -> NullnessUtil.castNonNull(pathfinder).findPath(currentCell, enemy.cell()).truncate(movementPoints + 1))
.map(path -> path.keepWhile(step -> step.cell().equals(currentCell) || step.cell().walkable())) // Truncate path to first unwalkable cell (may occur if the enemy cell is inaccessible or if other fighters block the path)
.filter(path -> path.size() > 1)
.map(path -> actions.move().create(ai.fighter(), path))
;
Expand All @@ -69,6 +71,13 @@ public Optional<Action> generate(AI<F> ai, ActionsFactory<F> actions) {
* Compute the cell cost for optimize the path finding
*/
private @Positive int cellCost(FightCell cell) {
// Fix #94 : Some cells are not accessible, but walkable/targetable using teleport.
// In this case the pathfinder will fail, so instead of ignoring unwalkable cells, simply set a very high cost,
// which allows the AI to generate a path to an inaccessible cell without throws a PathException
if (!cell.walkableIgnoreFighter()) {
return 1000;
}

// A fighter is on the cell : the cell is not walkable
// But the fighter may leave the place at the next turn
// The cost is higher than a simple detour, but permit to resolve a path blocked by a fighter
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,6 @@ void has() {

@Test
void all() {
assertEquals(3, repository.all().size());
assertEquals(4, repository.all().size());
}
}

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,6 @@ void preloadWithTriggers() throws SQLException, ContainerException {
service.preload(logger);

Mockito.verify(logger).info("Loading maps...");
Mockito.verify(logger).info(Mockito.eq("{} maps successfully loaded in {}ms"), Mockito.eq(3), Mockito.any());
Mockito.verify(logger).info(Mockito.eq("{} maps successfully loaded in {}ms"), Mockito.eq(4), Mockito.any());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
import fr.quatrevieux.araknemu.game.exploration.map.ExplorationMap;
import fr.quatrevieux.araknemu.game.exploration.map.ExplorationMapService;
import fr.quatrevieux.araknemu.game.fight.builder.ChallengeBuilder;
import fr.quatrevieux.araknemu.game.fight.builder.FightBuilder;
import fr.quatrevieux.araknemu.game.fight.castable.CastScope;
import fr.quatrevieux.araknemu.game.fight.castable.Castable;
import fr.quatrevieux.araknemu.game.fight.event.FightStarted;
Expand Down Expand Up @@ -311,6 +312,12 @@ public FightBuilder addSimpleTeam(PlayerFighter leader, List<Integer> places) {
return this;
}

public FightBuilder map(int mapId) {
map = loadFightMap(mapId);

return this;
}

public FightBuilder addSelf(Consumer<FighterBuilder> configurator) {
return addAlly(builder -> {
builder.player(player);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,49 @@ void success() {
assertEquals(0, turn.points().movementPoints());
}

@Test
void withAllyOnPathShouldBeCircumvented() {
configureFight(fb -> fb
.addSelf(builder -> builder.cell(151))
.addAlly(builder -> builder.cell(166))
.addEnemy(builder -> builder.cell(181))
);

generateAndPerformMove();

assertEquals(195, fighter.cell().id());
assertEquals(0, turn.points().movementPoints());
}

@Test
void whenAllyBlockAccess() {
configureFight(fb -> fb
.addSelf(builder -> builder.cell(211))
.addAlly(builder -> builder.cell(284))
.addEnemy(builder -> builder.cell(341))
);

generateAndPerformMove();

assertEquals(256, fighter.cell().id());
assertEquals(0, turn.points().movementPoints());
}

// See: https://github.com/Arakne/Araknemu/issues/94
@Test
void notAccessibleCellShouldTruncateToNearestCell() {
configureFight(fb -> fb
.map(10342)
.addSelf(builder -> builder.cell(155))
.addEnemy(builder -> builder.cell(69))
);

generateAndPerformMove();

assertEquals(126, fighter.cell().id());
assertEquals(1, turn.points().movementPoints());
}

@Test
void noMP() {
configureFight(fb -> fb
Expand Down

0 comments on commit 90cf7fc

Please sign in to comment.