Passed
Pull Request — master (#249)
by Vincent
13:26
created

fr.quatrevieux.araknemu.game.fight.ai.action.TeleportNearEnemy   A

Complexity

Total Complexity 14

Size/Duplication

Total Lines 124
Duplicated Lines 0 %

Test Coverage

Coverage 97.92%

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 57
dl 0
loc 124
ccs 47
cts 48
cp 0.9792
rs 10
c 1
b 0
f 0
wmc 14

7 Methods

Rating   Name   Duplication   Size   Complexity  
A Selector.Selector(FightCell,FightCell) 0 3 1
A Selector.push(Spell,FightCell) 0 10 2
A Selector.adjacent() 0 2 1
A selectBestTeleportTargetForSpell(SpellCaster,Selector,BattlefieldMap,Spell) 0 14 5
A Selector.action(F,ActionsFactory) 0 6 3
B generate(AI,ActionsFactory) 0 38 8
A initialize(AI) 0 8 1
1
/*
2
 * This file is part of Araknemu.
3
 *
4
 * Araknemu is free software: you can redistribute it and/or modify
5
 * it under the terms of the GNU Lesser General Public License as published by
6
 * the Free Software Foundation, either version 3 of the License, or
7
 * (at your option) any later version.
8
 *
9
 * Araknemu is distributed in the hope that it will be useful,
10
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12
 * GNU Lesser General Public License for more details.
13
 *
14
 * You should have received a copy of the GNU Lesser General Public License
15
 * along with Araknemu.  If not, see <https://www.gnu.org/licenses/>.
16
 *
17
 * Copyright (c) 2017-2020 Vincent Quatrevieux
18
 */
19
20
package fr.quatrevieux.araknemu.game.fight.ai.action;
21
22
import fr.arakne.utils.maps.CoordinateCell;
23
import fr.quatrevieux.araknemu.game.fight.ai.AI;
24
import fr.quatrevieux.araknemu.game.fight.ai.util.SpellCaster;
25
import fr.quatrevieux.araknemu.game.fight.ai.util.SpellsHelper;
26
import fr.quatrevieux.araknemu.game.fight.castable.Castable;
27
import fr.quatrevieux.araknemu.game.fight.fighter.ActiveFighter;
28
import fr.quatrevieux.araknemu.game.fight.fighter.PassiveFighter;
29
import fr.quatrevieux.araknemu.game.fight.map.BattlefieldMap;
30
import fr.quatrevieux.araknemu.game.fight.map.FightCell;
31
import fr.quatrevieux.araknemu.game.fight.turn.action.Action;
32
import fr.quatrevieux.araknemu.game.fight.turn.action.factory.ActionsFactory;
33
import fr.quatrevieux.araknemu.game.spell.Spell;
34
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
35
36
import java.util.Collections;
37
import java.util.Comparator;
38
import java.util.List;
39
import java.util.Optional;
40
import java.util.stream.Collectors;
41
42
/**
43
 * Try to teleport near enemy
44
 */
45 1
public final class TeleportNearEnemy<F extends ActiveFighter> implements ActionGenerator<F> {
46 1
    private List<Spell> teleportSpells = Collections.emptyList();
47
48
    @Override
49
    public void initialize(AI<F> ai) {
50 1
        final SpellsHelper helper = ai.helper().spells();
51
52 1
        teleportSpells = helper
53 1
            .withEffect(4)
54 1
            .sorted(Comparator.comparingInt(Castable::apCost))
55 1
            .collect(Collectors.toList())
56
        ;
57 1
    }
58
59
    @Override
60
    public Optional<Action> generate(AI<F> ai, ActionsFactory<F> actions) {
61 1
        if (teleportSpells.isEmpty()) {
62 1
            return Optional.empty();
63
        }
64
65 1
        final int actionPoints = ai.turn().points().actionPoints();
66
67 1
        if (actionPoints < 1) {
68 1
            return Optional.empty();
69
        }
70
71 1
        final Optional<? extends PassiveFighter> enemy = ai.enemy();
72
73 1
        if (!enemy.isPresent()) {
74
            return Optional.empty();
75
        }
76
77 1
        final SpellCaster caster = ai.helper().spells().caster(actions.cast().validator());
78 1
        final Selector selector = new Selector(enemy.get().cell(), ai.fighter().cell());
79
80
        // Already at adjacent cell of the enemy
81 1
        if (selector.adjacent()) {
82 1
            return Optional.empty();
83
        }
84
85
        // Spells are ordered by AP cost : the first spell which can reach an accessible adjacent is necessarily the best spell
86 1
        for (Spell spell : teleportSpells) {
87 1
            if (spell.apCost() > actionPoints) {
88 1
                break; // Following spells have an higher cost
89
            }
90
91 1
            if (selectBestTeleportTargetForSpell(caster, selector, ai.map(), spell)) {
92 1
                return selector.action(ai.fighter(), actions);
93
            }
94 1
        }
95
96 1
        return selector.action(ai.fighter(), actions);
97
    }
98
99
    /**
100
     * Select the best possible target for the given spell
101
     * The result will be push()'ed into selector
102
     *
103
     * @return true if the spell can reach an adjacent cell
104
     */
105
    private boolean selectBestTeleportTargetForSpell(SpellCaster caster, Selector selector, BattlefieldMap map, Spell spell) {
106 1
        for (FightCell cell : map) {
107
            // Target or launch is not valid
108 1
            if (!cell.walkable() || !caster.validate(spell, cell)) {
109 1
                continue;
110
            }
111
112
            // Adjacent cell found : no need to continue
113 1
            if (selector.push(spell, cell)) {
114 1
                return true;
115
            }
116 1
        }
117
118 1
        return false;
119
    }
120
121
    /**
122
     * Select the best spell and cell couple for teleport
123
     */
124
    private class Selector {
125
        private final CoordinateCell<FightCell> enemyCell;
126
        private int distance;
127
        private @MonotonicNonNull FightCell cell;
128
        private @MonotonicNonNull Spell spell;
129
130 1
        public Selector(FightCell enemyCell, FightCell currentCell) {
131 1
            this.enemyCell = enemyCell.coordinate();
132 1
            this.distance = this.enemyCell.distance(currentCell);
133 1
        }
134
135
        /**
136
         * Check if the current cell is adjacent to the enemy cell
137
         */
138
        public boolean adjacent() {
139 1
            return distance == 1;
140
        }
141
142
        /**
143
         * Push the teleport parameters and check if there are better than the previous
144
         *
145
         * @return true if the new cell is adjacent to the target
146
         */
147
        public boolean push(Spell spell, FightCell cell) {
148 1
            final int currentDistance = enemyCell.distance(cell);
149
150 1
            if (currentDistance < distance) {
151 1
                this.spell = spell;
152 1
                this.cell = cell;
153 1
                this.distance = currentDistance;
154
            }
155
156 1
            return adjacent();
157
        }
158
159
        /**
160
         * Get the best cast action
161
         * May returns an empty optional if no teleport spell can be found, or if the fighter is already on the best cell
162
         */
163
        public Optional<Action> action(F fighter, ActionsFactory<F> actions) {
164 1
            if (spell == null || cell == null) {
165 1
                return Optional.empty();
166
            }
167
168 1
            return Optional.of(actions.cast().create(fighter, spell, cell));
169
        }
170
    }
171
}
172