Passed
Branch dev (d24a17)
by Etienne
01:34
created

playerJoinTeam(TeamDto,PlayerEntity)   B

Complexity

Conditions 7

Size

Total Lines 59
Code Lines 30

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 59
rs 7.76
c 0
b 0
f 0
eloc 30
cc 7

How to fix   Long Method   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
package de.kleiner3.lasertag.mixin;
2
3
import com.google.gson.Gson;
4
import de.kleiner3.lasertag.lasertaggame.settings.LasertagSettingsManager;
5
import de.kleiner3.lasertag.LasertagMod;
6
import de.kleiner3.lasertag.block.entity.LaserTargetBlockEntity;
7
import de.kleiner3.lasertag.item.Items;
8
import de.kleiner3.lasertag.item.LasertagVestItem;
9
import de.kleiner3.lasertag.item.LasertagWeaponItem;
10
import de.kleiner3.lasertag.lasertaggame.ILasertagGame;
11
import de.kleiner3.lasertag.lasertaggame.ITickable;
12
import de.kleiner3.lasertag.lasertaggame.PlayerDeactivatedManager;
13
import de.kleiner3.lasertag.lasertaggame.statistics.GameStats;
14
import de.kleiner3.lasertag.lasertaggame.statistics.StatsCalculator;
15
import de.kleiner3.lasertag.lasertaggame.timing.GameTickTimerTask;
16
import de.kleiner3.lasertag.networking.NetworkingConstants;
17
import de.kleiner3.lasertag.networking.server.ServerEventSending;
18
import de.kleiner3.lasertag.lasertaggame.settings.SettingNames;
19
import de.kleiner3.lasertag.lasertaggame.teammanagement.TeamConfigManager;
20
import de.kleiner3.lasertag.lasertaggame.teammanagement.TeamDto;
21
import de.kleiner3.lasertag.common.types.Tuple;
22
import de.kleiner3.lasertag.lasertaggame.teammanagement.serialize.TeamDtoSerializer;
23
import io.netty.buffer.Unpooled;
24
import net.fabricmc.fabric.api.networking.v1.PacketByteBufs;
25
import net.fabricmc.fabric.api.networking.v1.ServerPlayNetworking;
26
import net.minecraft.entity.EquipmentSlot;
27
import net.minecraft.entity.player.PlayerEntity;
28
import net.minecraft.item.ItemStack;
29
import net.minecraft.network.PacketByteBuf;
30
import net.minecraft.server.MinecraftServer;
31
import net.minecraft.server.network.ServerPlayerEntity;
32
import net.minecraft.server.world.ServerWorld;
33
import net.minecraft.util.math.BlockPos;
34
import net.minecraft.world.World;
35
import org.spongepowered.asm.mixin.Mixin;
36
import org.spongepowered.asm.mixin.Shadow;
37
import org.spongepowered.asm.mixin.injection.At;
38
import org.spongepowered.asm.mixin.injection.Inject;
39
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
40
41
import java.util.*;
42
import java.util.concurrent.Executors;
43
import java.util.concurrent.ScheduledExecutorService;
44
import java.util.concurrent.TimeUnit;
45
46
/**
47
 * Interface injection into MinecraftServer to implement the lasertag game
48
 *
49
 * @author Étienne Muser
50
 */
51
@Mixin(MinecraftServer.class)
52
public abstract class MinecraftServerMixin implements ILasertagGame, ITickable {
53
    //region Private fields
54
55
    private HashMap<TeamDto, ArrayList<BlockPos>> spawnpointCache = null;
56
57
    /**
58
     * Map every player to their team
59
     */
60
    private final HashMap<TeamDto, List<PlayerEntity>> teamMap = new HashMap<>();
61
62
    private final StatsCalculator statsCalculator = new StatsCalculator(teamMap);
63
64
    private List<LaserTargetBlockEntity> lasertargetsToReset = new LinkedList<>();
65
66
    private boolean isRunning = false;
67
68
    private ScheduledExecutorService gameTickTimer = null;
69
70
    //endregion
71
72
    @Shadow
73
    public abstract ServerWorld getOverworld();
74
75
    /**
76
     * Inject into constructor of MinecraftServer
77
     *
78
     * @param ci The CallbackInfo
79
     */
80
    @Inject(method = "<init>", at = @At("TAIL"))
81
    private void init(CallbackInfo ci) {
82
83
        // Initialize team map
84
        for (TeamDto teamDto : TeamConfigManager.teamConfig.values()) {
85
            teamMap.put(teamDto, new LinkedList<>());
86
        }
87
    }
88
89
    //region ILasertagGame
90
91
    @Override
92
    public void startGame(boolean scanSpawnpoints) {
93
        // Reset all scores
94
        for (List<PlayerEntity> team : teamMap.values()) {
95
            for (PlayerEntity player : team) {
96
                player.resetLasertagScore();
97
            }
98
        }
99
        notifyPlayersAboutUpdate();
100
101
        // If spawnpoint cache needs to be filled
102
        if (spawnpointCache == null || scanSpawnpoints) {
103
            initSpawnpointCache();
104
        }
105
106
        // Get world
107
        World world = ((MinecraftServer) (Object) this).getOverworld();
108
109
        // Teleport players
110
        for (TeamDto teamDto : TeamConfigManager.teamConfig.values()) {
111
            List<PlayerEntity> team = teamMap.get(teamDto);
112
113
            for (PlayerEntity player : team) {
114
                // Get spawnpoints
115
                List<BlockPos> spawnpoints = spawnpointCache.get(teamDto);
116
117
                // If there are spawnpoints for this team
118
                if (spawnpoints.size() > 0) {
119
                    int idx = world.getRandom().nextInt(spawnpoints.size());
120
121
                    BlockPos destination = spawnpoints.get(idx);
122
                    player.requestTeleport(destination.getX() + 0.5, destination.getY() + 1, destination.getZ() + 0.5);
123
                }
124
            }
125
        }
126
127
        // Start game
128
        isRunning = true;
129
130
        var preGameDelayTimer = Executors.newSingleThreadScheduledExecutor();
131
        var preGameDelay = (long)LasertagSettingsManager.get(SettingNames.START_TIME);
132
        preGameDelayTimer.schedule(() -> {
133
134
            // Activate every player
135
            for (List<PlayerEntity> team : teamMap.values()) {
136
                for (PlayerEntity player : team) {
137
                    PlayerDeactivatedManager.activate(player.getUuid(), world);
138
                    player.onActivated();
139
                }
140
            }
141
142
            // Start game tick timer
143
            gameTickTimer = Executors.newSingleThreadScheduledExecutor();
144
            gameTickTimer.scheduleAtFixedRate(new GameTickTimerTask(this), 0, 1, TimeUnit.MINUTES);
145
146
        }, preGameDelay, TimeUnit.SECONDS);
147
148
        // Notify players
149
        sendGameStartedEvent();
150
    }
151
152
    @Override
153
    public List<PlayerEntity> getPlayersOfTeam(TeamDto teamDto) {
154
        return teamMap.get(teamDto);
155
    }
156
157
    @Override
158
    public void playerJoinTeam(TeamDto newTeamDto, PlayerEntity player) {
159
        // Get new team
160
        var newTeam = teamMap.get(newTeamDto);
161
162
        // Check if team is full
163
        if (newTeam.size() >= (long)LasertagSettingsManager.get(SettingNames.MAX_TEAM_SIZE)) {
164
            // If is Server
165
            if (player instanceof ServerPlayerEntity) {
166
                ServerEventSending.sendErrorMessageToClient((ServerPlayerEntity) player, "Team " + newTeamDto.name() + " is full.");
167
            }
168
            return;
169
        }
170
171
        // Check if player is in a team already
172
        TeamDto oldTeamDto = null;
173
        for (TeamDto t : TeamConfigManager.teamConfig.values()) {
174
            if (teamMap.get(t).contains(player)) {
175
                oldTeamDto = t;
176
                break;
177
            }
178
        }
179
180
        // If player has no team
181
        if (oldTeamDto == null) {
182
            newTeam.add(player);
183
        } else {
184
            // If player tries to join his team again
185
            if (newTeamDto == oldTeamDto) {
186
                return;
187
            }
188
189
            // Swap team
190
            teamMap.get(oldTeamDto).remove(player);
191
            newTeam.add(player);
192
        }
193
194
        // Set team on player
195
        player.setTeam(newTeamDto);
196
197
        // Get players inventory
198
        var inventory = player.getInventory();
199
200
        // Clear players inventory
201
        inventory.clear();
202
203
        // Give player a lasertag vest
204
        var vestStack = new ItemStack(Items.LASERTAG_VEST);
205
        ((LasertagVestItem) Items.LASERTAG_VEST).setColor(vestStack, newTeamDto.color().getValue());
206
        player.equipStack(EquipmentSlot.CHEST, vestStack);
207
208
        // Give player a lasertag weapon
209
        var weaponStack = new ItemStack(Items.LASERTAG_WEAPON);
210
        ((LasertagWeaponItem) Items.LASERTAG_WEAPON).setColor(weaponStack, newTeamDto.color().getValue());
211
        ((LasertagWeaponItem) Items.LASERTAG_WEAPON).setDeactivated(weaponStack, true);
212
        inventory.setStack(0, weaponStack);
213
214
        // Notify about change
215
        notifyPlayersAboutUpdate();
216
    }
217
218
    @Override
219
    public void playerLeaveHisTeam(PlayerEntity player) {
220
        // For each team
221
        for (List<PlayerEntity> team : teamMap.values()) {
222
            // If the player is in the team
223
            if (team.contains(player)) {
224
                // Leave the team
225
                team.remove(player);
226
                player.setTeam(null);
227
                notifyPlayersAboutUpdate();
228
                return;
229
            }
230
        }
231
    }
232
233
    @Override
234
    public void onPlayerScored(PlayerEntity player, int score) {
235
        player.increaseScore(score);
236
237
        notifyPlayersAboutUpdate();
238
    }
239
240
    @Override
241
    public boolean isLasertagGameRunning() {
242
        return isRunning;
243
    }
244
245
    @Override
246
    public void syncTeamsAndScoresToPlayer(ServerPlayerEntity player) {
247
        var simplifiedTeamMap = buildSimplifiedTeamMap();
248
249
        // Serialize team map to json
250
        String messagesString = new Gson().toJson(simplifiedTeamMap);
251
252
        // Create packet buffer
253
        PacketByteBuf buf = new PacketByteBuf(Unpooled.buffer());
254
255
        // Write team map string to buffer
256
        buf.writeString(messagesString);
257
258
        ServerPlayNetworking.send(player, NetworkingConstants.LASERTAG_GAME_TEAM_OR_SCORE_UPDATE, buf);
259
    }
260
261
    @Override
262
    public void registerLasertarget(LaserTargetBlockEntity target) {
263
        lasertargetsToReset.add(target);
264
    }
265
266
    //endregion
267
268
    //region ITickable
269
270
    /**
271
     * This method is called every minute when the game is running
272
     */
273
    @Override
274
    public void doTick() {
275
        System.out.println("Tick");
276
        // TODO
277
    }
278
279
    @Override
280
    public void endTick() {
281
        gameTickTimer.shutdown();
282
        gameTickTimer = null;
283
        lasertagGameOver();
284
    }
285
286
    //endregion
287
288
    //region Private methods
289
290
    /**
291
     * This method is called when the game ends
292
     */
293
    private void lasertagGameOver() {
294
        isRunning = false;
295
296
        sendGameOverEvent();
297
298
        // Get world
299
        World world = getOverworld();
300
301
        // Deactivate every player
302
        for (List<PlayerEntity> team : teamMap.values()) {
303
            for (PlayerEntity player : team) {
304
                PlayerDeactivatedManager.deactivate(player, world, true);
305
                player.onDeactivated();
306
            }
307
        }
308
309
        // Teleport players back to spawn
310
        for (var team : teamMap.values()) {
311
            for (var player : team) {
312
                player.requestTeleport(0.5F, 1, 0.5F);
313
            }
314
        }
315
316
        // Reset lasertargets
317
        for (var target : lasertargetsToReset) {
318
            target.reset();
319
        }
320
        lasertargetsToReset = new LinkedList<>();
321
322
        // Calculate stats
323
        statsCalculator.calcStats();
324
325
        // Create packet
326
        var buf = new PacketByteBuf(Unpooled.buffer());
327
328
        // Get last games stats
329
        var stats = statsCalculator.getLastGamesStats();
330
331
        // Get gson builder
332
        var gsonBuilder = TeamDtoSerializer.getSerializer();
333
334
        // To json
335
        var jsonString = gsonBuilder.create().toJson(stats, GameStats.class);
336
337
        // Write to buffer
338
        buf.writeString(jsonString);
339
340
        // Send statistics to clients
341
        ServerEventSending.sendToEveryone(getOverworld(), NetworkingConstants.GAME_STATISTICS, buf);
342
343
    }
344
345
    /**
346
     * Notifies every player of this world about a team or score update
347
     */
348
    private void notifyPlayersAboutUpdate() {
349
        var simplifiedTeamMap = buildSimplifiedTeamMap();
350
351
        // Serialize team map to json
352
        String messagesString = new Gson().toJson(simplifiedTeamMap);
353
354
        // Create packet buffer
355
        PacketByteBuf buf = new PacketByteBuf(Unpooled.buffer());
356
357
        // Write team map string to buffer
358
        buf.writeString(messagesString);
359
360
        // Send to all clients
361
        ServerEventSending.sendToEveryone(((MinecraftServer) (Object) this).getOverworld(), NetworkingConstants.LASERTAG_GAME_TEAM_OR_SCORE_UPDATE, buf);
362
    }
363
364
    private HashMap<String, List<Tuple<String, Integer>>> buildSimplifiedTeamMap() {
365
        // Create simplified team map
366
        final HashMap<String, List<Tuple<String, Integer>>> simplifiedTeamMap = new HashMap<>();
367
368
        // For each team
369
        for (TeamDto t : TeamConfigManager.teamConfig.values()) {
370
            // Create a new list of (player name, player score) tuples
371
            List<Tuple<String, Integer>> playerDatas = new LinkedList<>();
372
373
            // For every player in the team
374
            for (PlayerEntity player : teamMap.get(t)) {
375
                // Add his name and score to the list
376
                playerDatas.add(new Tuple<>(player.getDisplayName().getString(), player.getLasertagScore()));
377
            }
378
379
            // Add the current team to the simplified team map
380
            simplifiedTeamMap.put(t.name(), playerDatas);
381
        }
382
383
        return simplifiedTeamMap;
384
    }
385
386
    /**
387
     * Notifies every player of this world about the start of a lasertag game
388
     */
389
    private void sendGameStartedEvent() {
390
        ServerWorld world = ((MinecraftServer) (Object) this).getOverworld();
391
        ServerEventSending.sendToEveryone(world, NetworkingConstants.GAME_STARTED, PacketByteBufs.empty());
392
    }
393
394
    private void sendGameOverEvent() {
395
        ServerWorld world = ((MinecraftServer) (Object) this).getOverworld();
396
        ServerEventSending.sendToEveryone(world, NetworkingConstants.GAME_OVER, PacketByteBufs.empty());
397
    }
398
399
    /**
400
     * Initializes the spawnpoint cache. Searches a 31 x 31 chunk area for spawnpoint blocks specified by the team.
401
     * This method is computationally intensive, don't call too often or when responsiveness is important. The call of this method blocks the server from ticking!
402
     */
403
    private void initSpawnpointCache() {
404
405
        // Initialize cache
406
        spawnpointCache = new HashMap<>();
407
408
        // Initialize team lists
409
        for (TeamDto team : TeamConfigManager.teamConfig.values()) {
410
            spawnpointCache.put(team, new ArrayList<>());
411
        }
412
413
        // Get the overworld
414
        ServerWorld world = ((MinecraftServer) (Object) this).getOverworld();
415
416
        // Start time measurement
417
        long startTime = System.nanoTime();
418
419
        // Iterate over blocks and find spawnpoints
420
        world.fastSearchBlock((block, pos) -> {
421
            for (TeamDto teamDto : TeamConfigManager.teamConfig.values()) {
422
                if (teamDto.spawnpointBlock().equals(block)) {
423
                    var team = spawnpointCache.get(teamDto);
424
                    synchronized (teamDto) {
425
                        team.add(pos);
426
                    }
427
                    break;
428
                }
429
            }
430
        }, (currChunk, maxChunk) -> {
431
            // Only send a progress update every second chunk to not ddos our players
432
            if (currChunk % 2 == 0) {
433
                return;
434
            }
435
436
            // Create packet buffer
437
            PacketByteBuf buf = new PacketByteBuf(Unpooled.buffer());
438
439
            // Write progress to buffer
440
            buf.writeDouble((double) currChunk / (double) maxChunk);
441
442
            ServerEventSending.sendToEveryone(world, NetworkingConstants.PROGRESS, buf);
443
        });
444
445
        // Stop time measurement
446
        long stopTime = System.nanoTime();
447
        double duration = (stopTime - startTime) / 1000000000.0F;
448
        LasertagMod.LOGGER.info("Spawnpoint search took " + duration + "s.");
449
    }
450
451
    //endregion
452
}