targets()   B
last analyzed

Complexity

Conditions 8

Size

Total Lines 32
Code Lines 19

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 18
CRAP Score 8

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 19
c 1
b 0
f 0
dl 0
loc 32
ccs 18
cts 18
cp 1
rs 7.3333
cc 8
crap 8
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.BattlefieldCell;
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 class BaseCastScope<F extends FighterData, C extends BattlefieldCell> implements CastScope<F, C> {
44
    private final Castable action;
45
    private final F caster;
46
    private final C target;
47
48
    private final List<EffectScope> effects;
49 1
    private final Map<F, @Nullable F> targetMapping = new HashMap<>();
50
51 1
    protected BaseCastScope(Castable action, F caster, C 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
    @Override
69
    @Pure
70
    public final Castable action() {
71 1
        return action;
72
    }
73
74
    @Override
75
    public final Optional<Spell> spell() {
76 1
        if (action instanceof Spell) {
77 1
            return Optional.of((Spell) action);
78
        } else {
79 1
            return Optional.empty();
80
        }
81
    }
82
83
    @Override
84
    @Pure
85
    public final F caster() {
86 1
        return caster;
87
    }
88
89
    @Override
90
    @Pure
91
    public final C target() {
92 1
        return target;
93
    }
94
95
    @Override
96
    public final Set<F> targets() {
97 1
        return new HashSet<>(targetMapping.keySet());
98
    }
99
100
    /**
101
     * Replace a target of the cast
102
     *
103
     * @param originalTarget The base target fighter
104
     * @param newTarget The new target fighter
105
     */
106
    public final void replaceTarget(F originalTarget, F newTarget) {
107 1
        targetMapping.put(originalTarget, newTarget);
108
109
        // Add new target as target if not yet defined
110 1
        if (!targetMapping.containsKey(newTarget)) {
111 1
            targetMapping.put(newTarget, newTarget);
112
        }
113 1
    }
114
115
    /**
116
     * Remove a target of the cast
117
     *
118
     * Note: this method will definitively remove the target,
119
     * even if {@link BaseCastScope#replaceTarget(FighterData, FighterData)} is called
120
     */
121
    public final void removeTarget(F target) {
122
        // Set target to null without remove the key to ensure that it will effectively remove
123
        // even if a replaceTarget() point to it
124 1
        targetMapping.put(target, null);
125 1
    }
126
127
    @Override
128
    @Pure
129
    public final List<EffectScope> effects() {
130 1
        return effects;
131
    }
132
133
    /**
134
     * Resolve the target mapping
135
     *
136
     * @param baseTarget The base target of the effect
137
     *
138
     * @return Resolved target. Null if the target is removed
139
     */
140
    private @Nullable F resolveTarget(F baseTarget) {
141 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...
142
143
        // Target is removed, or it's the original one : do not resolve chaining
144 1
        if (target == null || target.equals(baseTarget)) {
145 1
            return target;
146
        }
147
148
        // Keep list of visited mapping to break recursion
149 1
        final Set<F> resolved = new HashSet<>();
150
151 1
        resolved.add(baseTarget);
152 1
        resolved.add(target);
153
154
        // Resolve chaining
155
        for (;;) {
156 1
            target = targetMapping.get(target);
157
158
            // The target is removed, or already visited (can be itself)
159 1
            if (target == null || resolved.contains(target)) {
160 1
                return target;
161
            }
162
163 1
            resolved.add(target);
164
        }
165
    }
166
167
    public final class EffectScope implements CastScope.EffectScope<F> {
0 ignored issues
show
Comprehensibility introduced by
Class or interface names should not shadow other classes or interfaces. In general, shadowing is a bad practice as it makes code harder to understand. Consider renaming this class.
Loading history...
168
        private final SpellEffect effect;
169
        private final Collection<F> targets;
170
171 1
        public EffectScope(SpellEffect effect, Collection<F> targets) {
172 1
            this.effect = effect;
173 1
            this.targets = targets;
174 1
        }
175
176
        /**
177
         * The related effect
178
         */
179
        @Pure
180
        public SpellEffect effect() {
181 1
            return effect;
182
        }
183
184
        /**
185
         * Get all targeted fighters for the current effect
186
         */
187
        public Collection<F> targets() {
188 1
            F firstTarget = null;
189 1
            Collection<F> resolvedTargets = null;
190
191 1
            for (F baseTarget : targets) {
192 1
                final F resolved = resolveTarget(baseTarget);
193
194 1
                if (resolved == null || resolved.dead()) {
195 1
                    continue;
196
                }
197
198 1
                if (firstTarget == null) {
199 1
                    firstTarget = resolved;
200
                } else {
201 1
                    if (resolvedTargets == null) {
202 1
                        resolvedTargets = new ArrayList<>();
203 1
                        resolvedTargets.add(firstTarget);
204
                    }
205
206 1
                    resolvedTargets.add(resolved);
207
                }
208 1
            }
209
210 1
            if (resolvedTargets != null) {
211 1
                return resolvedTargets;
212
            }
213
214 1
            if (firstTarget != null) {
215 1
                return Collections.singleton(firstTarget);
216
            }
217
218 1
            return Collections.emptyList();
219
        }
220
    }
221
}
222