Test Failed
Pull Request — master (#190)
by Vincent
16:13
created

run()   A

Complexity

Conditions 3

Size

Total Lines 14
Code Lines 12

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 12

Importance

Changes 0
Metric Value
cc 3
eloc 12
dl 0
loc 14
ccs 0
cts 0
cp 0
crap 12
rs 9.8
c 0
b 0
f 0
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;
21
22
import fr.quatrevieux.araknemu.core.event.DefaultListenerAggregate;
23
import fr.quatrevieux.araknemu.core.event.Dispatcher;
24
import fr.quatrevieux.araknemu.core.event.ListenerAggregate;
25
import fr.quatrevieux.araknemu.game.fight.castable.effect.EffectsHandler;
26
import fr.quatrevieux.araknemu.game.fight.event.FightCancelled;
27
import fr.quatrevieux.araknemu.game.fight.event.FightLeaved;
28
import fr.quatrevieux.araknemu.game.fight.event.FightStarted;
29
import fr.quatrevieux.araknemu.game.fight.event.FightStopped;
30
import fr.quatrevieux.araknemu.game.fight.exception.InvalidFightStateException;
31
import fr.quatrevieux.araknemu.game.fight.fighter.Fighter;
32
import fr.quatrevieux.araknemu.game.fight.map.FightMap;
33
import fr.quatrevieux.araknemu.game.fight.module.FightModule;
34
import fr.quatrevieux.araknemu.game.fight.state.FightState;
35
import fr.quatrevieux.araknemu.game.fight.state.StatesFlow;
36
import fr.quatrevieux.araknemu.game.fight.team.FightTeam;
37
import fr.quatrevieux.araknemu.game.fight.turn.FightTurnList;
38
import fr.quatrevieux.araknemu.game.fight.type.FightType;
39
import fr.quatrevieux.araknemu.game.world.util.Sender;
40
import org.apache.commons.lang3.time.StopWatch;
41
import org.apache.logging.log4j.Logger;
42
43
import java.time.Duration;
44
import java.util.ArrayList;
45
import java.util.HashMap;
46
import java.util.List;
47
import java.util.Map;
48
import java.util.concurrent.ScheduledExecutorService;
49
import java.util.concurrent.ScheduledFuture;
50
import java.util.concurrent.TimeUnit;
51
import java.util.concurrent.locks.Lock;
52
import java.util.concurrent.locks.ReentrantLock;
53
import java.util.stream.Collectors;
54
55
/**
56
 * Handle fight
57
 */
58
public final class Fight implements Dispatcher, Sender {
59
    private final int id;
60
    private final FightType type;
61
    private final FightMap map;
62
    private final List<FightTeam> teams;
63
    private final StatesFlow statesFlow;
64
    private final Logger logger;
65 1
    private final List<FightModule> modules = new ArrayList<>();
66 1
    private final Map<Class, Object> attachments = new HashMap<>();
67
    private final ListenerAggregate dispatcher;
68
    private final ScheduledExecutorService executor;
69
70 1
    private final Lock executorLock = new ReentrantLock();
71 1
    private final FightTurnList turnList = new FightTurnList(this);
72 1
    private final EffectsHandler effects = new EffectsHandler();
73
74 1
    private final StopWatch duration = new StopWatch();
75
    private volatile boolean alive = true;
76 1
77 1
    public Fight(int id, FightType type, FightMap map, List<FightTeam> teams, StatesFlow statesFlow, Logger logger, ScheduledExecutorService executor) {
78 1
        this.id = id;
79 1
        this.type = type;
80 1
        this.map = map;
81 1
        this.teams = teams;
82 1
        this.statesFlow = statesFlow;
83 1
        this.logger = logger;
84 1
        this.executor = executor;
85 1
        this.dispatcher = new DefaultListenerAggregate(logger);
86
    }
87
88
    /**
89
     * Register a module
90
     */
91 1
    public void register(FightModule module) {
92 1
        modules.add(module);
93 1
        dispatcher.register(module);
94 1
        module.effects(effects);
95
    }
96
97
    /**
98
     * Get the fight id
99
     */
100 1
    public int id() {
101
        return id;
102
    }
103
104
    /**
105
     * Get all teams
106
     */
107 1
    public List<FightTeam> teams() {
108
        return teams;
109
    }
110
111
    /**
112
     * Get one team
113
     *
114
     * @param number The team number
115
     */
116 1
    public FightTeam team(int number) {
117
        return teams.get(number);
118
    }
119
120
    /**
121
     * Get all fighters on the fight
122
     */
123 1
    public List<Fighter> fighters() {
124 1
        return teams
125 1
            .stream()
126 1
            .flatMap(fightTeam -> fightTeam.fighters().stream())
127 1
            .filter(Fighter::isOnFight)
128
            .collect(Collectors.toList())
129
        ;
130
    }
131
132
    /**
133
     * Get all fighters
134
     *
135
     * @param onlyInitialized true to returns only initialized fighter (is on the fight)
136
     */
137 1
    public List<Fighter> fighters(boolean onlyInitialized) {
138 1
        if (onlyInitialized) {
139
            return fighters();
140
        }
141 1
142 1
        return teams
143 1
            .stream()
144 1
            .flatMap(fightTeam -> fightTeam.fighters().stream())
145
            .collect(Collectors.toList())
146
        ;
147
    }
148
149
    /**
150
     * Get the fight map
151
     */
152 1
    public FightMap map() {
153
        return map;
154
    }
155
156
    /**
157
     * Get the current fight state
158
     */
159 1
    public FightState state() {
160
        return statesFlow.current();
161
    }
162
163
    /**
164
     * Get the current fight state if the type corresponds
165
     */
166
    @SuppressWarnings("unchecked")
167 1
    public <T extends FightState> T state(Class<T> type) {
168 1
        if (!type.isInstance(statesFlow.current())) {
169
            throw new InvalidFightStateException(type);
170
        }
171 1
172
        return (T) statesFlow.current();
173
    }
174
175
    /**
176
     * Start the next fight state
177
     */
178 1
    public void nextState() {
179 1
        statesFlow.next(this);
180 1
        modules.forEach(module -> module.stateChanged(statesFlow.current()));
181
    }
182
183
    /**
184
     * Get the fight type
185
     */
186 1
    public FightType type() {
187
        return type;
188
    }
189
190
    /**
191
     * Get the turn list
192
     */
193 1
    public FightTurnList turnList() {
194
        return turnList;
195
    }
196
197
    /**
198
     * Get the fight effects handle
199
     */
200 1
    public EffectsHandler effects() {
201
        return effects;
202
    }
203
204
    @Override
205 1
    public void send(Object packet) {
206
        final String sPacket = packet.toString();
207 1
208 1
        for (FightTeam team : teams) {
209 1
            team.send(sPacket);
210 1
        }
211
    }
212
213
    @Override
214 1
    public void dispatch(Object event) {
215 1
        dispatcher.dispatch(event);
216
    }
217
218
    /**
219
     * Schedule an action to perform in fight with delay
220
     *
221
     * @param action Action to execute
222
     * @param delay The delay
223
     */
224 1
    public ScheduledFuture<?> schedule(Runnable action, Duration delay) {
0 ignored issues
show
Comprehensibility introduced by
Remove usage of generic wildcard type.
Loading history...
225
        if (!alive) {
226
            throw new IllegalStateException("The fight is not alive");
227 1
        }
228 1
229 1
        return executor.schedule(new Task(action), delay.toMillis(), TimeUnit.MILLISECONDS);
230 1
    }
231
232 1
    /**
233
     * Execute an action into the fight executor thread
234 1
     *
235 1
     * @param action Action to execute
236
     */
237
    public void execute(Runnable action) {
238
        if (!alive) {
239
            throw new IllegalStateException("The fight is not alive");
240
        }
241
242
        executor.execute(new Task(action));
243
    }
244
245
    /**
246 1
     * Get the fight dispatcher
247
     */
248 1
    public ListenerAggregate dispatcher() {
249 1
        return dispatcher;
250 1
    }
251 1
252
    /**
253 1
     * Start the fight
254
     */
255 1
    public void start() {
256 1
        schedule(turnList::start, Duration.ofMillis(200));
257
        dispatch(new FightStarted(this));
258
259
        duration.start();
260
    }
261
262 1
    /**
263
     * Stop the fight
264
     */
265
    public void stop() {
266
        duration.stop();
267
        turnList.stop();
268
269 1
        dispatch(new FightStopped(this));
270 1
    }
271
272 1
    /**
273 1
     * Cancel the fight
274
     *
275
     * Must be called before start the fight
276
     */
277
    public void cancel() {
278
        cancel(false);
279 1
    }
280 1
281
    /**
282 1
     * Cancel the fight
283 1
     *
284
     * @param force Force cancel the fight even if the fight is started
285
     */
286
    public void cancel(boolean force) {
287
        if (!force && active()) {
288
            throw new IllegalStateException("Cannot cancel an active fight");
289
        }
290
291 1
        fighters().forEach(fighter -> fighter.dispatch(new FightLeaved()));
292 1
        dispatch(new FightCancelled(this));
293
        destroy();
294
    }
295
296
    /**
297
     * Get the fight duration in milliseconds
298
     */
299
    public long duration() {
300 1
        return duration.getTime();
301 1
    }
302
303
    /**
304 1
     * Check if the fight is active
305 1
     * The fight is active after the placement but before the end
306 1
     * To check if the fight is not finished nor cancelled, use {@link Fight#alive()}
307 1
     *
308
     * @see Fight#alive()
309
     */
310
    public boolean active() {
311
        return duration.isStarted();
312
    }
313 1
314
    /**
315
     * Check if the fight is not cancelled nor finished
316
     * A "dead" fight cannot be used anymore
317
     */
318
    public boolean alive() {
319
        return alive;
320 1
    }
321
322
    /**
323
     * Attach an object to the fight
324
     */
325
    public void attach(Object object) {
326
        attachments.put(object.getClass(), object);
327 1
    }
328 1
329
    /**
330
     * Get an attachment by its type
331
     */
332
    @SuppressWarnings("unchecked")
333
    public <T> T attachment(Class<T> type) {
334
        return (T) attachments.get(type);
335 1
    }
336
337
    /**
338
     * Destroy fight after terminated
339
     */
340
    public void destroy() {
341
        alive = false;
342 1
        teams.clear();
343 1
        map.destroy();
344 1
        attachments.clear();
345
    }
346
347
    /**
348
     * Wrap action to submit to executor to ensure that tasks are run sequentially (i.e. no task are run in parallel)
349
     */
350
    private final class Task implements Runnable {
351
        private final Runnable action;
352
353
        public Task(Runnable action) {
354
            this.action = action;
355
        }
356
357
        @Override
358
        public void run() {
359
            if (!alive) {
360
                logger.warn("Cannot run task " + action.getClass().toString() + " on dead fight");
361
                return;
362
            }
363
364
            try {
365
                executorLock.lock();
366
                action.run();
367
            } catch (Throwable e) {
368
                logger.error("Error on fight executor : " + e.getMessage(), e);
369
            } finally {
370
                executorLock.unlock();
371
            }
372
        }
373
    }
374
}
375