Passed
Pull Request — master (#190)
by Vincent
12:41
created

innerStartFight()   A

Complexity

Conditions 2

Size

Total Lines 7
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 2

Importance

Changes 0
Metric Value
cc 2
eloc 5
dl 0
loc 7
ccs 4
cts 4
cp 1
crap 2
rs 10
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-2019 Vincent Quatrevieux
18
 */
19
20
package fr.quatrevieux.araknemu.game.fight.state;
21
22
import fr.quatrevieux.araknemu.core.event.EventsSubscriber;
23
import fr.quatrevieux.araknemu.core.event.Listener;
24
import fr.quatrevieux.araknemu.game.fight.Fight;
25
import fr.quatrevieux.araknemu.game.fight.JoinFightError;
26
import fr.quatrevieux.araknemu.game.fight.ending.EndFightResults;
27
import fr.quatrevieux.araknemu.game.fight.ending.reward.FightRewardsSheet;
28
import fr.quatrevieux.araknemu.game.fight.event.FightJoined;
29
import fr.quatrevieux.araknemu.game.fight.event.FightLeaved;
30
import fr.quatrevieux.araknemu.game.fight.event.FighterAdded;
31
import fr.quatrevieux.araknemu.game.fight.event.FighterPlaceChanged;
32
import fr.quatrevieux.araknemu.game.fight.event.FighterRemoved;
33
import fr.quatrevieux.araknemu.game.fight.exception.FightException;
34
import fr.quatrevieux.araknemu.game.fight.exception.FightMapException;
35
import fr.quatrevieux.araknemu.game.fight.exception.InvalidFightStateException;
36
import fr.quatrevieux.araknemu.game.fight.exception.JoinFightException;
37
import fr.quatrevieux.araknemu.game.fight.fighter.Fighter;
38
import fr.quatrevieux.araknemu.game.fight.map.FightCell;
39
import fr.quatrevieux.araknemu.game.fight.map.util.PlacementCellsGenerator;
40
import fr.quatrevieux.araknemu.game.fight.team.FightTeam;
41
import fr.quatrevieux.araknemu.game.listener.fight.SendFighterPositions;
42
import fr.quatrevieux.araknemu.game.listener.fight.SendFighterReadyState;
43
import fr.quatrevieux.araknemu.game.listener.fight.SendNewFighter;
44
import fr.quatrevieux.araknemu.game.listener.fight.StartFightWhenAllReady;
45
import fr.quatrevieux.araknemu.game.listener.fight.fighter.ClearFighter;
46
import fr.quatrevieux.araknemu.game.listener.fight.fighter.SendFighterRemoved;
47
48
import java.util.Collection;
49
import java.util.Collections;
50
import java.util.HashMap;
51
import java.util.Map;
52
import java.util.concurrent.ScheduledFuture;
53
54
/**
55
 * Placement before start fight
56
 */
57
public final class PlacementState implements LeavableState, EventsSubscriber {
58
    private long startTime;
59
    private Fight fight;
60
    private Listener[] listeners;
61
    private Map<FightTeam, PlacementCellsGenerator> cellsGenerators;
62
    private ScheduledFuture<?> timer;
63
64
    private final boolean randomize;
65
66
    public PlacementState() {
67 1
        this(true);
68 1
    }
69
70 1
    public PlacementState(boolean randomize) {
71 1
        this.randomize = randomize;
72 1
    }
73
74
    @Override
75
    public void start(Fight fight) {
76 1
        this.fight = fight;
77 1
        this.cellsGenerators = new HashMap<>();
78
79 1
        for (FightTeam team : fight.teams()) {
80 1
            cellsGenerators.put(
81
                team,
82
                randomize
83 1
                    ? PlacementCellsGenerator.randomized(fight.map(), team.startPlaces())
84 1
                    : new PlacementCellsGenerator(fight.map(), team.startPlaces())
85
            );
86 1
        }
87
88 1
        fight.dispatcher().register(this);
89 1
        startTime = System.currentTimeMillis();
90 1
        addFighters(fight.fighters(false));
91
92 1
        if (fight.type().hasPlacementTimeLimit()) {
93 1
            timer = fight.schedule(this::innerStartFight, fight.type().placementDuration());
94
        }
95 1
    }
96
97
    @Override
98
    public int id() {
99 1
        return 2;
100
    }
101
102
    @Override
103
    public Listener[] listeners() {
104 1
        if (listeners != null) {
105 1
            return listeners;
106
        }
107
108 1
        return listeners = new Listener[] {
109
            new SendFighterPositions(fight),
110
            new SendFighterReadyState(fight),
111
            new StartFightWhenAllReady(fight, this),
112
            new SendNewFighter(fight),
113
            new ClearFighter(),
114
            new SendFighterRemoved(fight),
115
        };
116
    }
117
118
    /**
119
     * Get the remaining placement time, in milliseconds
120
     */
121
    public long remainingTime() {
122 1
        if (!fight.type().hasPlacementTimeLimit()) {
123 1
            throw new UnsupportedOperationException("The fight has no placement time limit");
124
        }
125
126 1
        return fight.type().placementDuration().toMillis() + startTime - System.currentTimeMillis();
127
    }
128
129
    /**
130
     * Try to change fighter start place
131
     *
132
     * @param fighter Fighter to move
133
     * @param cell The target cell
134
     */
135
    public synchronized void changePlace(Fighter fighter, FightCell cell) {
136 1
        if (invalidState()) {
137 1
            return;
138
        }
139
140 1
        if (fighter.ready()) {
141 1
            throw new FightException("The fighter is ready");
142
        }
143
144 1
        if (!cell.walkable()) {
145 1
            throw new FightMapException("Not walkable");
146
        }
147
148 1
        if (!fighter.team().startPlaces().contains(cell.id())) {
149 1
            throw new FightException("Bad start cell");
150
        }
151
152 1
        fighter.move(cell);
153 1
        fight.dispatch(new FighterPlaceChanged(fighter));
154 1
    }
155
156
    /**
157
     * Try to join a team
158
     *
159
     * @param fighter The fighter to add
160
     * @param team The team
161
     *
162
     * @throws JoinFightException When cannot join the team
163
     */
164
    public synchronized void joinTeam(Fighter fighter, FightTeam team) throws JoinFightException {
165 1
        if (invalidState()) {
166 1
            throw new JoinFightException(JoinFightError.CANT_DO_TOO_LATE);
167
        }
168
169 1
        team.join(fighter);
170 1
        addFighters(Collections.singleton(fighter));
171 1
    }
172
173
    @Override
174
    public synchronized void leave(Fighter fighter) {
175 1
        if (invalidState()) {
176 1
            throw new InvalidFightStateException(getClass());
177
        }
178
179
        // Not allowed to leave the fight : punish the fighter
180 1
        if (!fight.type().canCancel()) {
181 1
            punishDeserter(fighter);
182
        }
183
184
        // Remove fighter
185 1
        leaveFromFight(fighter);
186 1
    }
187
188
    /**
189
     * Manually start the fight
190
     */
191
    public synchronized void startFight() {
192
        // Try to cancel the timer
193 1
        if (timer != null) {
194 1
            if (!timer.cancel(false)) {
195
                return; // Should not occurs : the fight is already started by the timer
196
            }
197
        }
198
199 1
        innerStartFight();
200 1
    }
201
202
    /**
203
     * Start the fight
204
     */
205
    private void innerStartFight() {
206 1
        if (invalidState()) {
207 1
            return;
208
        }
209
210 1
        fight.dispatcher().unregister(this);
211 1
        fight.nextState();
212 1
    }
213
214
    /**
215
     * Leave from a fight :
216
     * - If the fighter is the team leader, the team is dissolved
217
     * - The fighter is removed
218
     * - Check if the fight is valid (has at least two teams)
219
     */
220
    private void leaveFromFight(Fighter fighter) {
221
        // The team leader quit the fight => Dissolve team
222 1
        if (fighter.isTeamLeader()) {
223 1
            fight.teams().remove(fighter.team());
224 1
            fighter.team().fighters().forEach(this::removeFighter);
225
        } else {
226 1
            fighter.team().kick(fighter);
227 1
            removeFighter(fighter);
228
        }
229
230 1
        checkFightValid();
231 1
    }
232
233
    /**
234
     * Punish the deserter fighter
235
     */
236
    private void punishDeserter(Fighter fighter) {
237 1
        final FightRewardsSheet rewardsSheet = fight.type().rewards().generate(
238
            new EndFightResults(
239
                fight,
240 1
                Collections.emptyList(),
241 1
                Collections.singletonList(fighter)
242
            )
243
        );
244
245 1
        fighter.dispatch(new FightLeaved(rewardsSheet.rewards().get(0)));
246 1
    }
247
248
    private void addFighters(Collection<Fighter> fighters) {
249 1
        for (Fighter fighter : fighters) {
250 1
            fighter.joinFight(fight, cellsGenerators.get(fighter.team()).next());
251 1
        }
252
253 1
        for (Fighter fighter : fighters) {
254 1
            fighter.dispatch(new FightJoined(fight, fighter));
255 1
        }
256
257 1
        for (Fighter fighter : fighters) {
258 1
            fight.dispatch(new FighterAdded(fighter));
259 1
        }
260 1
    }
261
262
    /**
263
     * Notify fight that a fighter has leave
264
     */
265
    private void removeFighter(Fighter fighter) {
266 1
        fighter.dispatch(new FightLeaved());
267 1
        fight.dispatch(new FighterRemoved(fighter, fight));
268 1
    }
269
270
    /**
271
     * Check if the fight is valid after fighter leaved
272
     */
273
    private void checkFightValid() {
274 1
        if (fight.teams().stream().filter(FightTeam::alive).count() > 1) {
275 1
            return;
276
        }
277
278 1
        fight.cancel();
279
280 1
        if (timer != null) {
281 1
            timer.cancel(true);
282
        }
283 1
    }
284
285
    /**
286
     * Check if the fight state is not placement
287
     * This method will return true is the state is active (or following), or if it's cancelled or finished
288
     */
289
    private boolean invalidState() {
290 1
        return fight.state() != this || !fight.alive();
291
    }
292
}
293