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

invalidState()   A

Complexity

Conditions 1

Size

Total Lines 2
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
cc 1
eloc 2
dl 0
loc 2
ccs 0
cts 0
cp 0
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 1
66 1
    public PlacementState() {
67
        this(true);
68 1
    }
69 1
70 1
    public PlacementState(boolean randomize) {
71
        this.randomize = randomize;
72
    }
73
74 1
    @Override
75 1
    public void start(Fight fight) {
76
        this.fight = fight;
77 1
        this.cellsGenerators = new HashMap<>();
78 1
79
        for (FightTeam team : fight.teams()) {
80
            cellsGenerators.put(
81 1
                team,
82 1
                randomize
83
                    ? PlacementCellsGenerator.randomized(fight.map(), team.startPlaces())
84 1
                    : new PlacementCellsGenerator(fight.map(), team.startPlaces())
85
            );
86 1
        }
87 1
88 1
        fight.dispatcher().register(this);
89
        startTime = System.currentTimeMillis();
90 1
        addFighters(fight.fighters(false));
91 1
92
        if (fight.type().hasPlacementTimeLimit()) {
93 1
            timer = fight.schedule(this::innerStartFight, fight.type().placementDuration());
94
        }
95
    }
96
97 1
    @Override
98
    public int id() {
99
        return 2;
100
    }
101
102 1
    @Override
103 1
    public Listener[] listeners() {
104
        if (listeners != null) {
105
            return listeners;
106 1
        }
107
108
        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 1
     */
121 1
    public long remainingTime() {
122
        if (!fight.type().hasPlacementTimeLimit()) {
123
            throw new UnsupportedOperationException("The fight has no placement time limit");
124 1
        }
125
126
        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 1
     */
135 1
    public synchronized void changePlace(Fighter fighter, FightCell cell) {
136
        if (invalidState()) {
137
            return;
138 1
        }
139 1
140
        if (fighter.ready()) {
141
            throw new FightException("The fighter is ready");
142 1
        }
143 1
144
        if (!cell.walkable()) {
145
            throw new FightMapException("Not walkable");
146 1
        }
147 1
148 1
        if (!fighter.team().startPlaces().contains(cell.id())) {
149
            throw new FightException("Bad start cell");
150
        }
151
152
        fighter.move(cell);
153
        fight.dispatch(new FighterPlaceChanged(fighter));
154
    }
155
156
    /**
157
     * Try to join a team
158
     *
159 1
     * @param fighter The fighter to add
160 1
     * @param team The team
161
     *
162
     * @throws JoinFightException When cannot join the team
163 1
     */
164 1
    public synchronized void joinTeam(Fighter fighter, FightTeam team) throws JoinFightException {
165 1
        if (invalidState()) {
166
            throw new JoinFightException(JoinFightError.CANT_DO_TOO_LATE);
167
        }
168
169 1
        team.join(fighter);
170 1
        addFighters(Collections.singleton(fighter));
171
    }
172
173
    @Override
174 1
    public synchronized void leave(Fighter fighter) {
175 1
        if (invalidState()) {
176
            throw new InvalidFightStateException(getClass());
177
        }
178
179 1
        // Not allowed to leave the fight : punish the fighter
180 1
        if (!fight.type().canCancel()) {
181
            punishDeserter(fighter);
182
        }
183
184
        // Remove fighter
185
        leaveFromFight(fighter);
186 1
    }
187 1
188
    /**
189
     * Manually start the fight
190 1
     */
191 1
    public synchronized void startFight() {
192 1
        // Try to cancel the timer
193
        if (timer != null) {
194
            if (!timer.cancel(false)) {
195
                return; // Should not occurs : the fight is already started by the timer
196
            }
197
        }
198
199
        innerStartFight();
200
    }
201
202 1
    /**
203 1
     * Start the fight
204 1
     */
205
    private void innerStartFight() {
206 1
        if (invalidState()) {
207 1
            return;
208
        }
209
210 1
        fight.dispatcher().unregister(this);
211 1
        fight.nextState();
212
    }
213
214
    /**
215
     * Leave from a fight :
216
     * - If the fighter is the team leader, the team is dissolved
217 1
     * - The fighter is removed
218
     * - Check if the fight is valid (has at least two teams)
219
     */
220 1
    private void leaveFromFight(Fighter fighter) {
221 1
        // The team leader quit the fight => Dissolve team
222
        if (fighter.isTeamLeader()) {
223
            fight.teams().remove(fighter.team());
224
            fighter.team().fighters().forEach(this::removeFighter);
225 1
        } else {
226 1
            fighter.team().kick(fighter);
227
            removeFighter(fighter);
228
        }
229 1
230 1
        checkFightValid();
231 1
    }
232
233 1
    /**
234 1
     * Punish the deserter fighter
235 1
     */
236
    private void punishDeserter(Fighter fighter) {
237 1
        final FightRewardsSheet rewardsSheet = fight.type().rewards().generate(
238 1
            new EndFightResults(
239 1
                fight,
240 1
                Collections.emptyList(),
241
                Collections.singletonList(fighter)
242
            )
243
        );
244
245
        fighter.dispatch(new FightLeaved(rewardsSheet.rewards().get(0)));
246 1
    }
247 1
248 1
    private void addFighters(Collection<Fighter> fighters) {
249
        for (Fighter fighter : fighters) {
250
            fighter.joinFight(fight, cellsGenerators.get(fighter.team()).next());
251
        }
252
253
        for (Fighter fighter : fighters) {
254 1
            fighter.dispatch(new FightJoined(fight, fighter));
255 1
        }
256
257 1
        for (Fighter fighter : fighters) {
258
            fight.dispatch(new FighterAdded(fighter));
259
        }
260
    }
261
262
    /**
263
     * Notify fight that a fighter has leave
264
     */
265
    private void removeFighter(Fighter fighter) {
266
        fighter.dispatch(new FightLeaved());
267
        fight.dispatch(new FighterRemoved(fighter, fight));
268
    }
269
270
    /**
271
     * Check if the fight is valid after fighter leaved
272
     */
273
    private void checkFightValid() {
274
        if (fight.teams().stream().filter(FightTeam::alive).count() > 1) {
275
            return;
276
        }
277
278
        fight.cancel();
279
280
        if (timer != null) {
281
            timer.cancel(true);
282
        }
283
    }
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
        return fight.state() != this || !fight.alive();
291
    }
292
}
293