Passed
Pull Request — master (#224)
by Vincent
11:59
created

interval()   B

Complexity

Conditions 6

Size

Total Lines 14
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 5
CRAP Score 6

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 6
eloc 11
c 1
b 0
f 0
dl 0
loc 14
ccs 5
cts 5
cp 1
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-2021 Vincent Quatrevieux
18
 */
19
20
package fr.quatrevieux.araknemu.game.fight.castable.effect;
21
22
import fr.arakne.utils.value.Interval;
23
import fr.arakne.utils.value.helper.RandomUtil;
24
import fr.quatrevieux.araknemu.game.fight.fighter.PassiveFighter;
25
import fr.quatrevieux.araknemu.game.spell.effect.SpellEffect;
26
27
import java.util.function.BiConsumer;
28
29
/**
30
 * Handle effect jet value
31
 *
32
 * Computed value is :
33
 * ( [jet + boost] * percent / 100 + fixed + effectBonus ) * multiply
34
 */
35
public final class EffectValue implements Cloneable {
36 1
    enum State {
37 1
        MINIMIZED,
38 1
        RANDOMIZED,
39 1
        MAXIMIZED,
40 1
        FIXED,
41
    }
42
43
    /**
44
     * EffectValue is a short life object, and random is only used 1 time
45
     */
46 1
    private static final RandomUtil RANDOM = RandomUtil.createShared();
47
48
    private final SpellEffect effect;
49
50 1
    private State state = State.RANDOMIZED;
51 1
    private int boost = 0;
52 1
    private int percent = 100;
53 1
    private int fixed = 0;
54 1
    private int multiply = 1;
55 1
    private int value = 0;
56
57 1
    EffectValue(SpellEffect effect) {
58 1
        this.effect = effect;
59 1
    }
60
61
    /**
62
     * Maximize the value
63
     */
64
    public EffectValue maximize() {
65 1
        state = State.MAXIMIZED;
66
67 1
        return this;
68
    }
69
70
    /**
71
     * Minimize the value
72
     */
73
    public EffectValue minimize() {
74 1
        state = State.MINIMIZED;
75
76 1
        return this;
77
    }
78
79
    /**
80
     * The value will be a random value between [min, max]
81
     */
82
    public EffectValue randomize() {
83 1
        state = State.RANDOMIZED;
84
85 1
        return this;
86
    }
87
88
    /**
89
     * Roll the dice to fix the effect value
90
     */
91
    public EffectValue roll() {
92 1
        value = jet();
93 1
        state = State.FIXED;
94
95 1
        return this;
96
    }
97
98
    /**
99
     * Boost the dice value
100
     * The boost will be added at dice value
101
     * So the boosted value will be increased with percent
102
     *
103
     * Ex: [5, 10] + boost 5 + 50% => [15, 22]
104
     *
105
     * @param value The boosted value
106
     */
107
    public EffectValue boost(int value) {
108 1
        this.boost = value;
109
110 1
        return this;
111
    }
112
113
    /**
114
     * Boost with percent value
115
     *
116
     * Ex: [5, 10] + 50% => [7, 15]
117
     */
118
    public EffectValue percent(int value) {
119 1
        this.percent += value;
120
121 1
        return this;
122
    }
123
124
    /**
125
     * Add fixed value
126
     * The fixed value will be added after percent value
127
     *
128
     * Ex: [5, 10] + 5 fixed => [10, 15]
129
     */
130
    public EffectValue fixed(int value) {
131 1
        this.fixed += value;
132
133 1
        return this;
134
    }
135
136
    /**
137
     * Multiply the result
138
     * Unlike percent, the multiplier will be used at the end of the operation.
139
     * So, it multiplies jet, percent and fixed bonus
140
     */
141
    public EffectValue multiply(int value) {
142 1
        this.multiply = value;
143
144 1
        return this;
145
    }
146
147
    /**
148
     * Get the dice value
149
     */
150
    public int value() {
151 1
        return applyBoost(jet());
152
    }
153
154
    /**
155
     * Get the effect value interval
156
     */
157
    public Interval interval() {
158 1
        switch (state) {
159
            case FIXED:
160 1
                return Interval.of(value);
161
162
            case MINIMIZED:
163 1
                return Interval.of(applyBoost(effect.min()));
164
165
            case MAXIMIZED:
166 1
                return Interval.of(applyBoost(Math.max(effect.max(), effect.min())));
167
168
            case RANDOMIZED:
169
            default:
170 1
                return new Interval(effect.min(), Math.max(effect.max(), effect.min())).map(this::applyBoost);
171
        }
172
    }
173
174
    @Override
175
    protected EffectValue clone() {
0 ignored issues
show
introduced by
Remove this "clone" implementation; use a copy constructor or copy factory instead.
Loading history...
176
        try {
177 1
            return (EffectValue) super.clone();
178
        } catch (CloneNotSupportedException e) {
179
            throw new RuntimeException(); // Should not occur
0 ignored issues
show
Best Practice introduced by
Dedicated exceptions should be preferred over throwing the generic Exception.
Loading history...
180
        }
181
    }
182
183
    /**
184
     * Create and configure an effect value for a given caster and target
185
     *
186
     * @param effect The spell effect
187
     * @param caster The spell caster on which {@link fr.quatrevieux.araknemu.game.fight.castable.effect.buff.Buffs#onEffectValueCast(EffectValue)} will be called
188
     * @param target The target on which {@link fr.quatrevieux.araknemu.game.fight.castable.effect.buff.Buffs#onEffectValueTarget(EffectValue, PassiveFighter)} will be called
189
     *
190
     * @return The configured effect
191
     */
192
    public static EffectValue create(SpellEffect effect, PassiveFighter caster, PassiveFighter target) {
193 1
        final EffectValue value = new EffectValue(effect);
194
195 1
        caster.buffs().onEffectValueCast(value);
196 1
        target.buffs().onEffectValueTarget(value, caster);
197
198 1
        return value;
199
    }
200
201
    /**
202
     * Create and configure multiple effect values for multiple targets
203
     *
204
     * Only one "dice" will be used for all targets, but each target will receive their own EffectValue,
205
     * configured using {@link fr.quatrevieux.araknemu.game.fight.castable.effect.buff.Buffs#onEffectValueTarget(EffectValue, PassiveFighter)}.
206
     *
207
     * So {@link EffectValue#minimize()} and {@link EffectValue#maximize()} are effective, without change the effects value of others targets
208
     *
209
     * Usage:
210
     * <pre>{@code
211
     * public void handle(CastScope cast, CastScope.EffectScope effect) {
212
     *     EffectValue.forEachTargets(effect.effect(), cast.caster(), effect.targets(), (target, effectValue) -> {
213
     *         // Apply the effect (effectValue) on target
214
     *         target.life().alter(cast.caster(), effectValue.value());
215
     *     });
216
     * }
217
     * }</pre>
218
     *
219
     * @param effect The spell effect
220
     * @param caster The spell caster on which {@link fr.quatrevieux.araknemu.game.fight.castable.effect.buff.Buffs#onEffectValueCast(EffectValue)} will be called
221
     * @param targets Targets used to configure the effect value using {@link fr.quatrevieux.araknemu.game.fight.castable.effect.buff.Buffs#onEffectValueTarget(EffectValue, PassiveFighter)}
222
     * @param action Action to perform on each target, with their related effect value
223
     */
224
    public static void forEachTargets(SpellEffect effect, PassiveFighter caster, Iterable<PassiveFighter> targets, BiConsumer<PassiveFighter, EffectValue> action) {
225 1
        final EffectValue value = new EffectValue(effect);
226
227 1
        caster.buffs().onEffectValueCast(value);
228 1
        value.roll();
229
230 1
        for (PassiveFighter target : targets) {
231 1
            final EffectValue targetValue = value.clone();
232 1
            target.buffs().onEffectValueTarget(targetValue, caster);
233
234 1
            action.accept(target, targetValue);
235 1
        }
236 1
    }
237
238
    private int jet() {
239 1
        switch (state) {
240
            case FIXED:
241 1
                return value;
242
243
            case MINIMIZED:
244 1
                return effect.min();
245
246
            case MAXIMIZED:
247 1
                return Math.max(effect.max(), effect.min());
248
249
            case RANDOMIZED:
250
            default:
251 1
                return RANDOM.rand(effect.min(), effect.max());
252
        }
253
    }
254
255
    private int applyBoost(int value) {
256 1
        return Math.max(
257 1
            ((boost + value) * percent / 100 + fixed + effect.boost()) * multiply,
258
            0
259
        );
260
    }
261
}
262