Passed
Push — master ( b3961b...2fc6ac )
by Vincent
06:35 queued 11s
created

FightService(Dispatcher,Collection,Collection,FightConfiguration)   A

Complexity

Conditions 1

Size

Total Lines 10

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 8
CRAP Score 1

Importance

Changes 0
Metric Value
cc 1
dl 0
loc 10
ccs 8
cts 8
cp 1
crap 1
rs 9.9
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-2020 Vincent Quatrevieux
18
 */
19
20
package fr.quatrevieux.araknemu.game.fight;
21
22
import fr.quatrevieux.araknemu.core.event.Dispatcher;
23
import fr.quatrevieux.araknemu.core.event.EventsSubscriber;
24
import fr.quatrevieux.araknemu.core.event.Listener;
25
import fr.quatrevieux.araknemu.game.GameConfiguration;
26
import fr.quatrevieux.araknemu.game.event.GameStopped;
27
import fr.quatrevieux.araknemu.game.exploration.event.ExplorationPlayerCreated;
28
import fr.quatrevieux.araknemu.game.exploration.map.ExplorationMap;
29
import fr.quatrevieux.araknemu.game.fight.builder.FightBuilder;
30
import fr.quatrevieux.araknemu.game.fight.builder.FightBuilderFactory;
31
import fr.quatrevieux.araknemu.game.fight.event.FightCreated;
32
import fr.quatrevieux.araknemu.game.fight.map.FightMap;
33
import fr.quatrevieux.araknemu.game.fight.module.FightModule;
34
import fr.quatrevieux.araknemu.game.listener.player.exploration.LeaveExplorationForFight;
35
import fr.quatrevieux.araknemu.game.listener.player.fight.AttachFighter;
36
import fr.quatrevieux.araknemu.game.player.event.PlayerLoaded;
37
import fr.quatrevieux.araknemu.util.ExecutorFactory;
38
39
import java.util.Collection;
40
import java.util.Collections;
41
import java.util.Map;
42
import java.util.NoSuchElementException;
43
import java.util.concurrent.ConcurrentHashMap;
44
import java.util.concurrent.ScheduledExecutorService;
45
import java.util.concurrent.atomic.AtomicInteger;
46
import java.util.function.Function;
47
import java.util.stream.Collectors;
48
49
/**
50
 * Service for create fights
51
 */
52
public final class FightService implements EventsSubscriber {
53
    private final Dispatcher dispatcher;
54
    private final Map<Class, FightBuilderFactory> builderFactories;
55
    private final Collection<FightModule.Factory> moduleFactories;
56
    private final GameConfiguration.FightConfiguration configuration;
0 ignored issues
show
Unused Code introduced by
Consider removing the unused private field configuration.
Loading history...
57
    private final ScheduledExecutorService executor;
58
59 1
    private final Map<Integer, Map<Integer, Fight>> fightsByMapId = new ConcurrentHashMap<>();
60 1
    private final AtomicInteger lastFightId = new AtomicInteger();
61
62 1
    public FightService(Dispatcher dispatcher, Collection<? extends FightBuilderFactory> factories, Collection<FightModule.Factory> moduleFactories, GameConfiguration.FightConfiguration configuration) {
63 1
        this.dispatcher = dispatcher;
64 1
        this.moduleFactories = moduleFactories;
65 1
        this.configuration = configuration;
66 1
        this.executor = ExecutorFactory.create(configuration.threadsCount());
67
68 1
        this.builderFactories = factories.stream().collect(
69 1
            Collectors.toMap(
70
                FightBuilderFactory::type,
71 1
                Function.identity()
72
            )
73
        );
74 1
    }
75
76
    @Override
77
    public Listener[] listeners() {
78 1
        return new Listener[] {
79 1
            new Listener<PlayerLoaded>() {
80
                @Override
81
                public void on(PlayerLoaded event) {
82 1
                    event.player().dispatcher().add(new AttachFighter(event.player()));
83 1
                }
84
85
                @Override
86
                public Class<PlayerLoaded> event() {
87 1
                    return PlayerLoaded.class;
88
                }
89
            },
90 1
            new Listener<ExplorationPlayerCreated>() {
91
                @Override
92
                public void on(ExplorationPlayerCreated event) {
93 1
                    event.player().dispatcher().add(new LeaveExplorationForFight(event.player()));
94 1
                }
95
96
                @Override
97
                public Class<ExplorationPlayerCreated> event() {
98 1
                    return ExplorationPlayerCreated.class;
99
                }
100
            },
101 1
            new Listener<GameStopped>() {
102
                @Override
103
                public void on(GameStopped event) {
104 1
                    fightsByMapId.values().stream()
105 1
                        .flatMap(fights -> fights.values().stream())
106 1
                        .forEach(fight -> fight.cancel(true))
107
                    ;
108
109 1
                    fightsByMapId.clear();
110 1
                    executor.shutdownNow();
111 1
                }
112
113
                @Override
114
                public Class<GameStopped> event() {
115 1
                    return GameStopped.class;
116
                }
117
            },
118
        };
119
    }
120
121
    /**
122
     * Create fight map
123
     *
124
     * @param map The base map
125
     */
126
    public FightMap map(ExplorationMap map) {
127 1
        return new FightMap(map.template());
128
    }
129
130
    /**
131
     * Create the fight handler
132
     *
133
     * @param type The build type
134
     */
135
    @SuppressWarnings("unchecked")
136
    public <B extends FightBuilder> FightHandler<B> handler(Class<B> type) {
137 1
        final FightBuilderFactory<B> builderFactory = builderFactories.get(type);
138
139 1
        if (builderFactory == null) {
140 1
            throw new NoSuchElementException("Builder for fight type " + type.getSimpleName() + " is not registered");
141
        }
142
143 1
        return new FightHandler<>(this, builderFactory.create(this, executor));
144
    }
145
146
    /**
147
     * Get all fights on the map
148
     *
149
     * @param mapId The map id
150
     */
151
    public Collection<Fight> fightsByMap(int mapId) {
152 1
        final Map<Integer, Fight> fightsOnMap = fightsByMapId.get(mapId);
153
154 1
        if (fightsOnMap != null) {
155 1
            return fightsOnMap.values();
156
        }
157
158 1
        return Collections.emptyList();
159
    }
160
161
    /**
162
     * Get all available fights
163
     * Note: this method can be really heavy to execute
164
     */
165
    public Collection<Fight> fights() {
166 1
        return fightsByMapId.values().stream()
167 1
            .flatMap(fights -> fights.values().stream())
168 1
            .collect(Collectors.toList())
169
        ;
170
    }
171
172
    /**
173
     * Get a fight by its id from a map
174
     *
175
     * @param mapId The map id
176
     * @param fightId The fight id
177
     */
178
    public Fight getFromMap(int mapId, int fightId) {
179 1
        final Map<Integer, Fight> fights = fightsByMapId.get(mapId);
180
        final Fight fight;
181
182 1
        if (fights != null && (fight = fights.get(fightId)) != null) {
183 1
            return fight;
184
        }
185
186 1
        throw new NoSuchElementException("Fight not found");
187
    }
188
189
    /**
190
     * Generate a new unique fight id
191
     */
192
    int newFightId() {
193 1
        return lastFightId.incrementAndGet();
194
    }
195
196
    /**
197
     * The fight is initialized
198
     */
199
    synchronized void created(Fight fight) {
200 1
        final Map<Integer, Fight> fights = fightsByMapId.computeIfAbsent(
201 1
            fight.map().id(),
202 1
            mapId -> new ConcurrentHashMap<>()
203
        );
204
205 1
        fights.put(fight.id(), fight);
206
207 1
        dispatcher.dispatch(new FightCreated(fight));
208 1
    }
209
210
    /**
211
     * Remove the fight
212
     */
213
    synchronized void remove(Fight fight) {
214 1
        final Map<Integer, Fight> fightsOnMap = fightsByMapId.get(fight.map().id());
215
216 1
        if (fightsOnMap != null) {
217 1
            fightsOnMap.remove(fight.id());
218
        }
219 1
    }
220
221
    /**
222
     * Make modules for a fight
223
     */
224
    Collection<FightModule> modules(Fight fight) {
225 1
        return moduleFactories.stream()
226 1
            .map(factory -> factory.create(fight))
227 1
            .collect(Collectors.toList())
228
        ;
229
    }
230
}
231