com.skelril.skree.content.zone.group.jungleraid.JungleRaidInstance.java Source code

Java tutorial

Introduction

Here is the source code for com.skelril.skree.content.zone.group.jungleraid.JungleRaidInstance.java

Source

/*
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 */

package com.skelril.skree.content.zone.group.jungleraid;

import com.flowpowered.math.vector.Vector3i;
import com.google.common.collect.Lists;
import com.nearce.gamechatter.sponge.GameChatterPlugin;
import com.skelril.nitro.Clause;
import com.skelril.nitro.entity.SafeTeleportHelper;
import com.skelril.nitro.probability.Probability;
import com.skelril.skree.content.zone.LegacyZoneBase;
import com.skelril.skree.service.PlayerStateService;
import com.skelril.skree.service.internal.playerstate.InventoryStorageStateException;
import com.skelril.skree.service.internal.zone.PlayerClassifier;
import com.skelril.skree.service.internal.zone.Zone;
import com.skelril.skree.service.internal.zone.ZoneRegion;
import com.skelril.skree.service.internal.zone.ZoneStatus;
import org.apache.commons.lang3.text.WordUtils;
import org.spongepowered.api.Sponge;
import org.spongepowered.api.block.BlockState;
import org.spongepowered.api.block.BlockTypes;
import org.spongepowered.api.block.trait.EnumTraits;
import org.spongepowered.api.data.key.Keys;
import org.spongepowered.api.data.meta.ItemEnchantment;
import org.spongepowered.api.data.type.DyeColor;
import org.spongepowered.api.data.type.DyeColors;
import org.spongepowered.api.entity.living.player.Player;
import org.spongepowered.api.item.Enchantments;
import org.spongepowered.api.item.ItemTypes;
import org.spongepowered.api.item.inventory.ItemStack;
import org.spongepowered.api.text.Text;
import org.spongepowered.api.text.channel.MessageChannel;
import org.spongepowered.api.text.format.TextColors;
import org.spongepowered.api.util.Color;
import org.spongepowered.api.world.Location;
import org.spongepowered.api.world.World;

import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.function.Supplier;

import static com.skelril.nitro.item.ItemStackFactory.newItemStack;
import static com.skelril.nitro.transformer.ForgeTransformer.tf;
import static com.skelril.skree.service.internal.zone.PlayerClassifier.PARTICIPANT;
import static com.skelril.skree.service.internal.zone.PlayerClassifier.SPECTATOR;

public class JungleRaidInstance extends LegacyZoneBase implements Zone, Runnable {

    private List<Player> participants = new ArrayList<>();

    private Map<Player, Set<Player>> teamMapping = new HashMap<>();
    private Set<Player> freeForAllPlayers = new HashSet<>();
    private Set<Player> blueTeamPlayers = new HashSet<>();
    private Set<Player> redTeamPlayers = new HashSet<>();
    private Map<Player, JungleRaidClass> classMap = new HashMap<>();
    private Map<Player, Player> lastAttackerMap = new HashMap<>();

    private JungleRaidState state = JungleRaidState.LOBBY;
    private long startTime;

    private Location<World> lobbySpawnLocation;
    private Location<World> leftFlagActivationSign;
    private Location<World> rightFlagActivationSign;
    private List<Location<World>> scrollingFlagSigns = new ArrayList<>();

    private Location<World> leftClassActivationSign;
    private Location<World> rightClassActivationSign;
    private List<Location<World>> scrollingClassSigns = new ArrayList<>();

    private int signScrollFlagStart;
    private int signScrollClassStart;

    private FlagEffectData flagData = new FlagEffectData();
    private boolean[] flagState = new boolean[JungleRaidFlag.values().length];

    public JungleRaidInstance(ZoneRegion region) {
        super(region);
    }

    @Override
    public boolean init() {
        setUp();
        remove();
        return true;
    }

    private void setUp() {
        Vector3i offset = getRegion().getMinimumPoint();

        lobbySpawnLocation = new Location<>(getRegion().getExtent(), offset.add(216, 2, 29));
        leftFlagActivationSign = new Location<>(getRegion().getExtent(), offset.add(209, 3, 29));
        rightFlagActivationSign = new Location<>(getRegion().getExtent(), offset.add(209, 3, 23));

        for (int z = 28; z > 23; --z) { // Do this in rerverse so left/right buttons are correct
            scrollingFlagSigns.add(new Location<>(getRegion().getExtent(), offset.add(209, 3, z)));
        }

        for (JungleRaidFlag flag : JungleRaidFlag.values()) {
            flagState[flag.index] = flag.enabledByDefault;
        }

        flagSignPopulate();

        leftClassActivationSign = new Location<>(getRegion().getExtent(), offset.add(209, 3, 22));
        rightClassActivationSign = new Location<>(getRegion().getExtent(), offset.add(209, 3, 18));

        for (int z = 21; z > 18; --z) { // Do this in rerverse so left/right buttons are correct
            scrollingClassSigns.add(new Location<>(getRegion().getExtent(), offset.add(209, 3, z)));
        }

        classSignPopulate();
    }

    private void updateFlagSign(int index) {
        String title = JungleRaidFlag.values()[signScrollFlagStart + index].toString();
        if (title.length() > 15) {
            title = title.substring(0, 15);
        }
        title = WordUtils.capitalizeFully(title.replace("_", " "));

        scrollingFlagSigns.get(index).getTileEntity().get().offer(Keys.SIGN_LINES,
                Lists.newArrayList(Text.EMPTY, Text.of(title),
                        Text.of(flagState[signScrollFlagStart + index] ? Text.of(TextColors.DARK_GREEN, "Enabled")
                                : Text.of(TextColors.RED, "Disabled")),
                        Text.EMPTY));
    }

    private void flagSignPopulate() {
        for (int i = 0; i < scrollingFlagSigns.size(); ++i) {
            updateFlagSign(i);
        }

        boolean isLeftScrollable = signScrollFlagStart == 0;
        leftFlagActivationSign.getTileEntity().get().offer(Keys.SIGN_LINES, Lists.newArrayList(Text.EMPTY,
                Text.of(isLeftScrollable ? "" : TextColors.BLUE, "<<"), Text.EMPTY, Text.EMPTY));
        boolean isRightScrollable = signScrollFlagStart
                + scrollingFlagSigns.size() == JungleRaidFlag.values().length;
        rightFlagActivationSign.getTileEntity().get().offer(Keys.SIGN_LINES, Lists.newArrayList(Text.EMPTY,
                Text.of(isRightScrollable ? "" : TextColors.BLUE, ">>"), Text.EMPTY, Text.EMPTY));
    }

    public Location<World> getLeftFlagActivationSign() {
        return leftFlagActivationSign;
    }

    public Location<World> getRightFlagActivationSign() {
        return rightFlagActivationSign;
    }

    public void leftFlagListSign() {
        signScrollFlagStart = Math.max(0, signScrollFlagStart - scrollingFlagSigns.size());
        flagSignPopulate();
    }

    public void rightFlagListSign() {
        signScrollFlagStart = Math.min(JungleRaidFlag.values().length - scrollingFlagSigns.size(),
                signScrollFlagStart + scrollingFlagSigns.size());
        flagSignPopulate();
    }

    public void tryToggleFlagSignAt(Location<World> loc) {
        for (int i = 0; i < scrollingFlagSigns.size(); ++i) {
            if (loc.equals(scrollingFlagSigns.get(i))) {
                flagState[signScrollFlagStart + i] = !flagState[signScrollFlagStart + i];
                updateFlagSign(i);
                break;
            }
        }
    }

    private void updateClassSign(int index) {
        String title = JungleRaidClass.values()[signScrollClassStart + index].toString();
        if (title.length() > 15) {
            title = title.substring(0, 15);
        }
        title = WordUtils.capitalizeFully(title.replace("_", " "));

        scrollingClassSigns.get(index).getTileEntity().get().offer(Keys.SIGN_LINES,
                Lists.newArrayList(Text.EMPTY, Text.of(title), Text.EMPTY, Text.EMPTY));
    }

    private void classSignPopulate() {
        for (int i = 0; i < scrollingClassSigns.size(); ++i) {
            updateClassSign(i);
        }

        boolean isLeftScrollable = signScrollClassStart == 0;
        leftClassActivationSign.getTileEntity().get().offer(Keys.SIGN_LINES, Lists.newArrayList(Text.EMPTY,
                Text.of(isLeftScrollable ? "" : TextColors.BLUE, "<<"), Text.EMPTY, Text.EMPTY));
        boolean isRightScrollable = signScrollClassStart
                + scrollingClassSigns.size() == JungleRaidClass.values().length;
        rightClassActivationSign.getTileEntity().get().offer(Keys.SIGN_LINES, Lists.newArrayList(Text.EMPTY,
                Text.of(isRightScrollable ? "" : TextColors.BLUE, ">>"), Text.EMPTY, Text.EMPTY));
    }

    public Location<World> getLeftClassActivationSign() {
        return leftClassActivationSign;
    }

    public Location<World> getRightClassActivationSign() {
        return rightClassActivationSign;
    }

    public void leftClassListSign() {
        signScrollClassStart = Math.max(0, signScrollClassStart - scrollingClassSigns.size());
        classSignPopulate();
    }

    public void rightClassListSign() {
        signScrollClassStart = Math.min(JungleRaidClass.values().length - scrollingClassSigns.size(),
                signScrollClassStart + scrollingClassSigns.size());
        classSignPopulate();
    }

    public void tryUseClassSignAt(Location<World> loc, Player player) {
        for (int i = 0; i < scrollingClassSigns.size(); ++i) {
            if (loc.equals(scrollingClassSigns.get(i))) {
                JungleRaidClass targetClass = JungleRaidClass.values()[signScrollClassStart + i];
                giveBaseEquipment(player, targetClass);
                classMap.put(player, targetClass);
                break;
            }
        }
    }

    public void setFlag(JungleRaidFlag flag, boolean enabled) {
        flagState[flag.index] = enabled;
    }

    public boolean isFlagEnabled(JungleRaidFlag flag) {
        return flagState[flag.index];
    }

    @Override
    public void forceEnd() {
        remove(getPlayers(PARTICIPANT));
        remove();
    }

    @Override
    public void run() {
        if (isEmpty() && state != JungleRaidState.IN_PROGRESS) {
            expire();
            return;
        }

        if (state == JungleRaidState.LOBBY) {
            smartStart();
            return;
        }

        if (state == JungleRaidState.INITIALIZE) {
            tryBeginCombat();
            return;
        }

        outOfBoundsCheck();

        Optional<Clause<String, WinType>> optWinner = getWinner();
        if (optWinner.isPresent()) {
            processWin(optWinner.get());
            expire();
            return;
        }
        JungleRaidEffectProcessor.run(this);
    }

    private void outOfBoundsCheck() {
        for (Player player : getPlayers(PARTICIPANT)) {
            if (contains(player)) {
                continue;
            }

            if (player.getLocation().add(0, -1, 0).getBlockType() == BlockTypes.AIR) {
                continue;
            }

            remove(player);
        }
    }

    public JungleRaidState getState() {
        return state;
    }

    public long getStartTime() {
        return startTime;
    }

    public FlagEffectData getFlagData() {
        return flagData;
    }

    private void tryBeginCombat() {
        if (System.currentTimeMillis() - startTime >= TimeUnit.MINUTES.toMillis(1)) {
            state = JungleRaidState.IN_PROGRESS;
            getPlayerMessageChannel(SPECTATOR).send(Text.of(TextColors.DARK_RED, "LET THE SLAUGHTER BEGIN!"));
        }
    }

    public Optional<Clause<String, WinType>> getWinner() {
        return getWinner(freeForAllPlayers, blueTeamPlayers, redTeamPlayers);
    }

    private Optional<Clause<String, WinType>> getWinner(Collection<Player> ffa, Collection<Player> blue,
            Collection<Player> red) {
        if (ffa.size() == 1 && blue.isEmpty() && red.isEmpty()) {
            return Optional.of(new Clause<>(ffa.iterator().next().getName(), WinType.SOLO));
        } else if (ffa.isEmpty() && !blue.isEmpty() && red.isEmpty()) {
            return Optional.of(new Clause<>("Blue", WinType.TEAM));
        } else if (ffa.isEmpty() && blue.isEmpty() && !red.isEmpty()) {
            return Optional.of(new Clause<>("Red", WinType.TEAM));
        } else if (ffa.isEmpty() && blue.isEmpty() && red.isEmpty()) {
            return Optional.of(new Clause<>(null, WinType.DRAW));
        }

        return Optional.empty();
    }

    private void processWin(Clause<String, WinType> winClause) {
        state = JungleRaidState.DONE;

        String rawWinMessage;
        switch (winClause.getValue()) {
        case SOLO:
            rawWinMessage = winClause.getKey() + " has won the jungle raid!";
            break;
        case TEAM:
            rawWinMessage = winClause.getKey() + " team has won the jungle raid!";
            break;
        case DRAW:
            rawWinMessage = "The jungle raid was a draw!";
            break;
        default:
            return;
        }

        MessageChannel.TO_ALL.send(Text.of(TextColors.GOLD, rawWinMessage));
        GameChatterPlugin.inst().sendSystemMessage(rawWinMessage);
    }

    @Override
    public Collection<Player> getPlayers(PlayerClassifier classifier) {
        if (classifier == PARTICIPANT) {
            return Lists.newArrayList(participants);
        }
        return super.getPlayers(classifier);
    }

    @Override
    public Clause<Player, ZoneStatus> add(Player player) {
        if (state != JungleRaidState.LOBBY) {
            return new Clause<>(player, ZoneStatus.NO_REJOIN);
        }

        player.setLocation(lobbySpawnLocation);
        Optional<PlayerStateService> optService = Sponge.getServiceManager().provide(PlayerStateService.class);
        if (optService.isPresent()) {
            PlayerStateService service = optService.get();
            try {
                service.storeInventory(player);
                service.releaseInventory(player);

                giveBaseEquipment(player, JungleRaidClass.BALANCED);
            } catch (InventoryStorageStateException e) {
                e.printStackTrace();
                return new Clause<>(player, ZoneStatus.ERROR);
            }
        }

        participants.add(player);

        return new Clause<>(player, ZoneStatus.ADDED);
    }

    private void giveBaseEquipment(Player player, JungleRaidClass jrClass) {
        player.getInventory().clear();

        List<ItemStack> gear = new ArrayList<>();
        switch (jrClass) {
        case MELEE:
            ItemStack enchantedSword = newItemStack(ItemTypes.IRON_SWORD);
            enchantedSword.offer(Keys.ITEM_ENCHANTMENTS,
                    Lists.newArrayList(new ItemEnchantment(Enchantments.FIRE_ASPECT, 2),
                            new ItemEnchantment(Enchantments.KNOCKBACK, 2)));

            gear.add(enchantedSword);
            break;
        case LUMBERJACK:
            ItemStack enchantedAxe = newItemStack(ItemTypes.DIAMOND_AXE);
            enchantedAxe.offer(Keys.ITEM_ENCHANTMENTS,
                    Lists.newArrayList(new ItemEnchantment(Enchantments.SHARPNESS, 3),
                            new ItemEnchantment(Enchantments.KNOCKBACK, 2)));

            gear.add(enchantedAxe);
            break;
        case ARCHER:
            ItemStack dmgBow = newItemStack(ItemTypes.BOW);
            dmgBow.offer(Keys.ITEM_ENCHANTMENTS, Lists.newArrayList(new ItemEnchantment(Enchantments.PUNCH, 2)));

            gear.add(dmgBow);

            ItemStack fireBow = newItemStack(ItemTypes.BOW);
            fireBow.offer(Keys.ITEM_ENCHANTMENTS, Lists.newArrayList(new ItemEnchantment(Enchantments.FLAME, 1)));

            gear.add(fireBow);
            break;
        case SNIPER:
            ItemStack superBow = newItemStack(ItemTypes.BOW);
            superBow.offer(Keys.ITEM_ENCHANTMENTS, Lists.newArrayList(new ItemEnchantment(Enchantments.POWER, 5),
                    new ItemEnchantment(Enchantments.FLAME, 1)));

            superBow.offer(Keys.ITEM_DURABILITY, jrClass.getArrowAmount());

            gear.add(superBow);

            ItemStack woodSword = newItemStack(ItemTypes.WOODEN_SWORD);
            gear.add(woodSword);
            break;
        case ENGINEER:
            ItemStack ironSword = newItemStack(ItemTypes.IRON_SWORD);
            gear.add(ironSword);

            ItemStack diamondPickaxe = newItemStack(ItemTypes.DIAMOND_PICKAXE);
            gear.add(diamondPickaxe);
            break;
        case BALANCED:
            ItemStack standardSword = newItemStack(ItemTypes.IRON_SWORD);
            gear.add(standardSword);

            ItemStack standardBow = newItemStack(ItemTypes.BOW);
            gear.add(standardBow);
            break;
        }

        int tntAmt = jrClass.getTNTAmount();
        int tntStacks = tntAmt / 64;
        int tntRemainder = tntAmt % 64;
        for (int i = 0; i < tntStacks; ++i) {
            gear.add(newItemStack(BlockTypes.TNT, 64));
        }
        if (tntRemainder > 0) {
            gear.add(newItemStack(BlockTypes.TNT, tntRemainder));
        }

        if (jrClass.hasFlintAndSteel()) {
            gear.add(newItemStack(ItemTypes.FLINT_AND_STEEL));
        }
        if (jrClass.hasShears()) {
            gear.add(newItemStack(ItemTypes.SHEARS));
        }
        if (jrClass.hasAxe()) {
            gear.add(newItemStack(ItemTypes.IRON_AXE));
        }
        gear.add(newItemStack(ItemTypes.COOKED_BEEF, 64));
        gear.add(newItemStack(ItemTypes.COMPASS));

        int arrowAmt = jrClass.getArrowAmount();
        int arrowStacks = arrowAmt / 64;
        int arrowRemainder = arrowAmt % 64;
        for (int i = 0; i < arrowStacks; ++i) {
            gear.add(newItemStack(ItemTypes.ARROW, 64));
        }
        if (arrowRemainder > 0) {
            gear.add(newItemStack(ItemTypes.ARROW, arrowRemainder));
        }

        for (ItemStack stack : gear) {
            player.getInventory().offer(stack);
        }

        tf(player).inventoryContainer.detectAndSendChanges();
    }

    private void giveTeamEquipment(Player player, Color teamColor) {
        ItemStack teamHood = newItemStack(ItemTypes.LEATHER_HELMET);
        teamHood.offer(Keys.DISPLAY_NAME, Text.of(TextColors.WHITE, "Team Hood"));
        teamHood.offer(Keys.COLOR, teamColor);
        player.setHelmet(teamHood);

        ItemStack teamChestplate = newItemStack(ItemTypes.LEATHER_CHESTPLATE);
        teamChestplate.offer(Keys.DISPLAY_NAME, Text.of(TextColors.WHITE, "Team Chestplate"));
        teamChestplate.offer(Keys.COLOR, teamColor);
        player.setChestplate(teamChestplate);

        ItemStack teamLeggings = newItemStack(ItemTypes.LEATHER_LEGGINGS);
        teamLeggings.offer(Keys.DISPLAY_NAME, Text.of(TextColors.WHITE, "Team Leggings"));
        teamLeggings.offer(Keys.COLOR, teamColor);
        player.setLeggings(teamLeggings);

        ItemStack teamBoots = newItemStack(ItemTypes.LEATHER_BOOTS);
        teamBoots.offer(Keys.DISPLAY_NAME, Text.of(TextColors.WHITE, "Team Boots"));
        teamBoots.offer(Keys.COLOR, teamColor);
        player.setBoots(teamBoots);
    }

    private void resetPlayerProperties(Player player) {
        player.offer(Keys.HEALTH, player.get(Keys.MAX_HEALTH).orElse(20D));
        player.offer(Keys.FOOD_LEVEL, 20);
        player.offer(Keys.SATURATION, 20D);
        player.offer(Keys.EXHAUSTION, 0D);

        player.offer(Keys.POTION_EFFECTS, new ArrayList<>());
    }

    private void addPlayer(Player player, Supplier<Location<World>> startingPos, Color teamColor,
            JungleRaidClass jrClass) {
        giveBaseEquipment(player, jrClass);
        giveTeamEquipment(player, teamColor);

        resetPlayerProperties(player);

        player.setLocation(startingPos.get());
    }

    public void addFFAPlayer(Player player, JungleRaidClass jrClass) {
        addPlayer(player, this::getRandomLocation, Color.WHITE, jrClass);
        freeForAllPlayers.add(player);
        teamMapping.put(player, freeForAllPlayers);
    }

    public void addBluePlayer(Player player, JungleRaidClass jrClass) {
        Location<World> spawnPoint = getRandomLocation();
        addPlayer(player, () -> spawnPoint, Color.BLUE, jrClass);
        blueTeamPlayers.add(player);
        teamMapping.put(player, blueTeamPlayers);
    }

    public void addRedPlayer(Player player, JungleRaidClass jrClass) {
        Location<World> spawnPoint = getRandomLocation();
        addPlayer(player, () -> spawnPoint, Color.RED, jrClass);
        redTeamPlayers.add(player);
        teamMapping.put(player, redTeamPlayers);
    }

    public void smartStart() {
        List<Player> ffaList = new ArrayList<>();
        List<Player> redList = new ArrayList<>();
        List<Player> blueList = new ArrayList<>();

        Collection<Player> containedPlayers = getPlayers(PARTICIPANT);
        if (containedPlayers.size() <= 1) {
            return;
        }

        for (Player player : containedPlayers) {
            BlockState state = player.getLocation().add(0, -1, 0).getBlock();
            if (state.getType() != BlockTypes.WOOL) {
                return;
            }

            Optional<?> optColor = state.getTraitValue(EnumTraits.WOOL_COLOR);
            if (optColor.isPresent()) {
                DyeColor color = (DyeColor) optColor.get();
                if (color == DyeColors.RED) {
                    redList.add(player);
                } else if (color == DyeColors.BLUE) {
                    blueList.add(player);
                } else if (color == DyeColors.WHITE) {
                    ffaList.add(player);
                } else {
                    return;
                }
            }
        }

        if (getWinner(ffaList, blueList, redList).isPresent()) {
            getPlayerMessageChannel(SPECTATOR)
                    .send(Text.of(TextColors.RED, "All players are on one team, the game will not start."));
            return;
        }

        ffaList.stream().forEach(p -> addFFAPlayer(p, classMap.getOrDefault(p, JungleRaidClass.BALANCED)));
        redList.stream().forEach(p -> addRedPlayer(p, classMap.getOrDefault(p, JungleRaidClass.BALANCED)));
        blueList.stream().forEach(p -> addBluePlayer(p, classMap.getOrDefault(p, JungleRaidClass.BALANCED)));

        state = JungleRaidState.INITIALIZE;
        startTime = System.currentTimeMillis();
    }

    public Location<World> getRandomLocation() {
        Vector3i offset = getRegion().getMinimumPoint();
        Vector3i boundingBox = getRegion().getBoundingBox();

        while (true) {
            Vector3i randomDest = new Vector3i(Probability.getRandom(boundingBox.getX()),
                    Probability.getRangedRandom(16, 80), Probability.getRandom(boundingBox.getZ())).add(offset);

            Optional<Location<World>> optSafeDest = SafeTeleportHelper
                    .getSafeDest(new Location<>(getRegion().getExtent(), randomDest));

            if (optSafeDest.isPresent()) {
                Location<World> safeDest = optSafeDest.get();
                if (16 < safeDest.getY() && safeDest.getY() < 79) {
                    return safeDest.add(.5, 0, .5);
                }
            }
        }
    }

    @Override
    public Clause<Player, ZoneStatus> remove(Player player) {
        resetPlayerProperties(player);
        playerLost(player);
        tryInventoryRestore(player);

        return super.remove(player);
    }

    public void tryInventoryRestore(Player player) {
        Optional<PlayerStateService> optService = Sponge.getServiceManager().provide(PlayerStateService.class);
        if (optService.isPresent()) {
            PlayerStateService service = optService.get();
            if (service.hasInventoryStored(player)) {
                try {
                    service.loadInventory(player);
                } catch (InventoryStorageStateException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    public void playerLost(Player player) {
        participants.remove(player);

        Set<Player> teamPlayers = teamMapping.remove(player);
        if (teamPlayers != null) {
            teamPlayers.remove(player);

            player.getInventory().clear();
        }
    }

    public Optional<Color> getTeamColor(Player player) {
        Set<Player> playerTeam = teamMapping.get(player);
        if (playerTeam == redTeamPlayers) {
            return Optional.of(Color.RED);
        } else if (playerTeam == blueTeamPlayers) {
            return Optional.of(Color.BLUE);
        } else if (playerTeam == freeForAllPlayers) {
            return Optional.of(Color.WHITE);
        }
        return Optional.empty();
    }

    public Optional<JungleRaidClass> getClass(Player player) {
        return Optional.ofNullable(classMap.get(player));
    }

    private void payPlayer(Player player) {

    }

    public void recordAttack(Player attacker, Player defender) {
        lastAttackerMap.put(defender, attacker);
    }

    public Optional<Player> getLastAttacker(Player defender) {
        return Optional.ofNullable(lastAttackerMap.get(defender));
    }

    public boolean isFriendlyFire(Player attacker, Player defender) {
        Set<Player> attackerTeam = teamMapping.get(attacker);
        Set<Player> defenderTeam = teamMapping.get(defender);

        /* We want identity comparison to prevent expensive list comparisons */
        return attackerTeam == defenderTeam && attackerTeam != freeForAllPlayers && attackerTeam != null;
    }
}