Passed
Pull Request — master (#281)
by Vincent
13:38
created

resolveTarget(F)   B

Complexity

Conditions 6

Size

Total Lines 24
Code Lines 12

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 10
CRAP Score 6

Importance

Changes 0
Metric Value
eloc 12
dl 0
loc 24
ccs 10
cts 10
cp 1
c 0
b 0
f 0
cc 6
crap 6
rs 8.6666
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.castable;
21
22
import fr.quatrevieux.araknemu.game.fight.fighter.FighterData;
23
import fr.quatrevieux.araknemu.game.fight.map.FightCell;
24
import fr.quatrevieux.araknemu.game.spell.Spell;
25
import fr.quatrevieux.araknemu.game.spell.effect.SpellEffect;
26
import org.checkerframework.checker.nullness.qual.Nullable;
27
import org.checkerframework.dataflow.qual.Pure;
28
29
import java.util.ArrayList;
30
import java.util.Collection;
31
import java.util.Collections;
32
import java.util.HashMap;
33
import java.util.HashSet;
34
import java.util.List;
35
import java.util.Map;
36
import java.util.Optional;
37
import java.util.Set;
38
import java.util.stream.Collectors;
39
40
/**
41
 * Wrap casting arguments
42
 */
43
public final class CastScope<F extends FighterData> {
44
    private final Castable action;
45
    private final F caster;
46
    private final FightCell target;
47
48
    private final List<EffectScope> effects;
49 1
    private final Map<F, @Nullable F> targetMapping = new HashMap<>();
50
51 1
    private CastScope(Castable action, F caster, FightCell target, List<SpellEffect> effects) {
52 1
        this.action = action;
53 1
        this.caster = caster;
54 1
        this.target = target;
55
56 1
        this.effects = effects.stream()
57 1
            .map(effect -> new EffectScope(effect, CastTargetResolver.resolveFromEffect(caster, target, action, effect)))
58 1
            .collect(Collectors.toList())
59
        ;
60
61 1
        for (EffectScope effect : this.effects) {
62 1
            for (F fighter : effect.targets) {
63 1
                this.targetMapping.put(fighter, fighter);
64 1
            }
65 1
        }
66 1
    }
67
68
    /**
69
     * Get the casted action
70
     */
71
    @Pure
72
    public Castable action() {
73 1
        return action;
74
    }
75
76
    /**
77
     * Get the spell, if and only if the action is a spell
78
     */
79
    public Optional<Spell> spell() {
80 1
        if (action instanceof Spell) {
81 1
            return Optional.of((Spell) action);
82
        } else {
83 1
            return Optional.empty();
84
        }
85
    }
86
87
    /**
88
     * Get the caster
89
     */
90
    @Pure
91
    public F caster() {
92 1
        return caster;
93
    }
94
95
    /**
96
     * Get the targeted cell
97
     */
98
    @Pure
99
    public FightCell target() {
100 1
        return target;
101
    }
102
103
    /**
104
     * Get the cast targets
105
     *
106
     * This method will not resolve target mapping, nor effect target mapping
107
     * It will return all targets, before the mapping is resolved
108
     * So if {@link CastScope#replaceTarget(FighterData, FighterData)} is called,
109
     * the new target will be added on this set
110
     *
111
     * Note: a new instance is returned to ensure that concurrent modification will not occur
112
     */
113
    public Set<F> targets() {
114 1
        return new HashSet<>(targetMapping.keySet());
115
    }
116
117
    /**
118
     * Replace a target of the cast
119
     *
120
     * @param originalTarget The base target fighter
121
     * @param newTarget The new target fighter
122
     */
123
    public void replaceTarget(F originalTarget, F newTarget) {
124 1
        targetMapping.put(originalTarget, newTarget);
125
126
        // Add new target as target if not yet defined
127 1
        if (!targetMapping.containsKey(newTarget)) {
128 1
            targetMapping.put(newTarget, newTarget);
129
        }
130 1
    }
131
132
    /**
133
     * Remove a target of the cast
134
     *
135
     * Note: this method will definitively remove the target,
136
     * even if {@link CastScope#replaceTarget(FighterData, FighterData)} is called
137
     */
138
    public void removeTarget(F target) {
139
        // Set target to null without remove the key to ensure that it will effectively remove
140
        // even if a replaceTarget() point to it
141 1
        targetMapping.put(target, null);
142 1
    }
143
144
    /**
145
     * Get list of effects to apply
146
     */
147
    @Pure
148
    public List<EffectScope> effects() {
149 1
        return effects;
150
    }
151
152
    /**
153
     * Create a basic CastScope instance
154
     * Should be used for weapons
155
     */
156
    public static <F extends FighterData> CastScope<F> simple(Castable action, F caster, FightCell target, List<SpellEffect> effects) {
157 1
        return new CastScope<>(action, caster, target, effects);
158
    }
159
160
    /**
161
     * Create a cast scope with probable effects (ex: Bluff)
162
     * This method must be used if the action has probable effects
163
     *
164
     * @see RandomEffectSelector#select(List)
165
     * @see SpellEffect#probability()
166
     */
167
    public static <F extends FighterData> CastScope<F> probable(Castable action, F caster, FightCell target, List<SpellEffect> effects) {
168 1
        return new CastScope<>(action, caster, target, RandomEffectSelector.select(effects));
169
    }
170
171
    /**
172
     * Resolve the target mapping
173
     *
174
     * @param baseTarget The base target of the effect
175
     *
176
     * @return Resolved target. Null if the target is removed
177
     */
178
    private @Nullable F resolveTarget(F baseTarget) {
179 1
        F target = targetMapping.get(baseTarget);
0 ignored issues
show
Comprehensibility introduced by
The variable targetshadows a field with the same name declared in line 46. Consider renaming it.
Loading history...
180
181
        // Target is removed, or it's the original one : do not resolve chaining
182 1
        if (target == null || target.equals(baseTarget)) {
183 1
            return target;
184
        }
185
186
        // Keep list of visited mapping to break recursion
187 1
        final Set<F> resolved = new HashSet<>();
188
189 1
        resolved.add(baseTarget);
190 1
        resolved.add(target);
191
192
        // Resolve chaining
193
        for (;;) {
194 1
            target = targetMapping.get(target);
195
196
            // The target is removed, or already visited (can be itself)
197 1
            if (target == null || resolved.contains(target)) {
198 1
                return target;
199
            }
200
201 1
            resolved.add(target);
202
        }
203
    }
204
205
    public final class EffectScope {
206
        private final SpellEffect effect;
207
        private final Collection<F> targets;
208
209 1
        public EffectScope(SpellEffect effect, Collection<F> targets) {
210 1
            this.effect = effect;
211 1
            this.targets = targets;
212 1
        }
213
214
        /**
215
         * The related effect
216
         */
217
        @Pure
218
        public SpellEffect effect() {
219 1
            return effect;
220
        }
221
222
        /**
223
         * Get all targeted fighters for the current effect
224
         */
225
        public Collection<F> targets() {
226 1
            F firstTarget = null;
227 1
            Collection<F> resolvedTargets = null;
228
229 1
            for (F baseTarget : targets) {
230 1
                final F resolved = resolveTarget(baseTarget);
231
232 1
                if (resolved == null || resolved.dead()) {
233 1
                    continue;
234
                }
235
236 1
                if (firstTarget == null) {
237 1
                    firstTarget = resolved;
238
                } else {
239 1
                    if (resolvedTargets == null) {
240 1
                        resolvedTargets = new ArrayList<>();
241 1
                        resolvedTargets.add(firstTarget);
242
                    }
243
244 1
                    resolvedTargets.add(resolved);
245
                }
246 1
            }
247
248 1
            if (resolvedTargets != null) {
249 1
                return resolvedTargets;
250
            }
251
252 1
            if (firstTarget != null) {
253 1
                return Collections.singleton(firstTarget);
254
            }
255
256 1
            return Collections.emptyList();
257
        }
258
    }
259
}
260