Package com.tommytony.war

Source Code of com.tommytony.war.Warzone

package com.tommytony.war;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Random;
import java.util.logging.Level;

import net.milkbowl.vault.economy.EconomyResponse;

import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.Validate;
import org.bukkit.Bukkit;
import org.bukkit.ChatColor;
import org.bukkit.GameMode;
import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.OfflinePlayer;
import org.bukkit.World;
import org.bukkit.block.Block;
import org.bukkit.block.BlockFace;
import org.bukkit.entity.Arrow;
import org.bukkit.entity.Entity;
import org.bukkit.entity.Item;
import org.bukkit.entity.Player;
import org.bukkit.entity.Projectile;
import org.bukkit.entity.TNTPrimed;
import org.bukkit.event.entity.EntityDamageByEntityEvent;
import org.bukkit.event.entity.EntityDamageEvent;
import org.bukkit.event.entity.EntityDamageEvent.DamageCause;
import org.bukkit.event.inventory.InventoryType;
import org.bukkit.event.player.PlayerMoveEvent;
import org.bukkit.inventory.InventoryView;
import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.PlayerInventory;
import org.bukkit.permissions.Permissible;
import org.bukkit.potion.PotionEffect;
import org.bukkit.scoreboard.DisplaySlot;
import org.bukkit.scoreboard.Objective;
import org.bukkit.scoreboard.Scoreboard;
import org.getspout.spoutapi.SpoutManager;
import org.getspout.spoutapi.player.SpoutPlayer;

import com.google.common.collect.ImmutableList;
import com.tommytony.war.config.InventoryBag;
import com.tommytony.war.config.ScoreboardType;
import com.tommytony.war.config.TeamConfig;
import com.tommytony.war.config.TeamConfigBag;
import com.tommytony.war.config.TeamKind;
import com.tommytony.war.config.WarzoneConfig;
import com.tommytony.war.config.WarzoneConfigBag;
import com.tommytony.war.event.WarBattleWinEvent;
import com.tommytony.war.event.WarPlayerLeaveEvent;
import com.tommytony.war.event.WarPlayerThiefEvent;
import com.tommytony.war.event.WarScoreCapEvent;
import com.tommytony.war.job.InitZoneJob;
import com.tommytony.war.job.LoadoutResetJob;
import com.tommytony.war.job.LogKillsDeathsJob;
import com.tommytony.war.job.LogKillsDeathsJob.KillsDeathsRecord;
import com.tommytony.war.mapper.LoadoutYmlMapper;
import com.tommytony.war.mapper.VolumeMapper;
import com.tommytony.war.mapper.ZoneVolumeMapper;
import com.tommytony.war.spout.SpoutDisplayer;
import com.tommytony.war.structure.Bomb;
import com.tommytony.war.structure.Cake;
import com.tommytony.war.structure.HubLobbyMaterials;
import com.tommytony.war.structure.Monument;
import com.tommytony.war.structure.WarzoneMaterials;
import com.tommytony.war.structure.ZoneLobby;
import com.tommytony.war.structure.ZoneWallGuard;
import com.tommytony.war.utility.Direction;
import com.tommytony.war.utility.Loadout;
import com.tommytony.war.utility.LoadoutSelection;
import com.tommytony.war.utility.PlayerState;
import com.tommytony.war.utility.PotionEffectHelper;
import com.tommytony.war.volume.Volume;
import com.tommytony.war.volume.ZoneVolume;

/**
*
* @author tommytony
* @package com.tommytony.war
*/
public class Warzone {
  public enum LeaveCause {
    COMMAND, DISCONNECT, SCORECAP, RESET;
    public boolean useRallyPoint() {
      return this == SCORECAP ? true : false;
    }
  }

  private String name;
  private ZoneVolume volume;
  private World world;
  private final List<Team> teams = new ArrayList<Team>();
  private final List<Monument> monuments = new ArrayList<Monument>();
  private final List<Bomb> bombs = new ArrayList<Bomb>();
  private final List<Cake> cakes = new ArrayList<Cake>();
  private Location teleport;
  private ZoneLobby lobby;
  private Location rallyPoint;
 
  private final List<String> authors = new ArrayList<String>();
 
  private final int minSafeDistanceFromWall = 6;
  private List<ZoneWallGuard> zoneWallGuards = new ArrayList<ZoneWallGuard>();
  private HashMap<String, PlayerState> playerStates = new HashMap<String, PlayerState>();
  private HashMap<String, Team> flagThieves = new HashMap<String, Team>();
  private HashMap<String, Bomb> bombThieves = new HashMap<String, Bomb>();
  private HashMap<String, Cake> cakeThieves = new HashMap<String, Cake>();
  private HashMap<String, LoadoutSelection> loadoutSelections = new HashMap<String, LoadoutSelection>();
  private HashMap<String, PlayerState> deadMenInventories = new HashMap<String, PlayerState>();
  private HashMap<String, Integer> killCount = new HashMap<String, Integer>();
  private final List<Player> respawn = new ArrayList<Player>();
  private final List<String> reallyDeadFighters = new ArrayList<String>();

  private List<LogKillsDeathsJob.KillsDeathsRecord> killsDeathsTracker = new ArrayList<KillsDeathsRecord>();
 
  private final WarzoneConfigBag warzoneConfig;
  private final TeamConfigBag teamDefaultConfig;
  private InventoryBag defaultInventories = new InventoryBag();

  private Scoreboard scoreboard;
 
  private HubLobbyMaterials lobbyMaterials = null;
  private WarzoneMaterials warzoneMaterials = new WarzoneMaterials(
      new ItemStack(Material.OBSIDIAN), new ItemStack(Material.FENCE),
      new ItemStack(Material.GLOWSTONE));
 
  private boolean isEndOfGame = false;
  private boolean isReinitializing = false;
  //private final Object gameEndLock = new Object();

  public Warzone(World world, String name) {
    this.world = world;
    this.name = name;
    this.warzoneConfig = new WarzoneConfigBag(this);
    this.teamDefaultConfig = new TeamConfigBag()// don't use ctor with Warzone, as this changes config resolution
    this.volume = new ZoneVolume(name, this.getWorld(), this);
    this.lobbyMaterials = War.war.getWarhubMaterials().clone();
  }

  public static Warzone getZoneByName(String name) {
    Warzone bestGuess = null;
    for (Warzone warzone : War.war.getWarzones()) {
      if (warzone.getName().toLowerCase().equals(name.toLowerCase())) {
        // perfect match, return right away
        return warzone;
      } else if (warzone.getName().toLowerCase().startsWith(name.toLowerCase())) {
        // perhaps there's a perfect match in the remaining zones, let's take this one aside
        bestGuess = warzone;
      }
    }
    return bestGuess;
  }

  public static Warzone getZoneByNameExact(String name) {
    for (Warzone zone : War.war.getWarzones()) {
      if (zone.getName().equalsIgnoreCase(name)) return zone;
    }
    return null;
  }

  public static Warzone getZoneByLocation(Location location) {
    for (Warzone warzone : War.war.getWarzones()) {
      if (location.getWorld().getName().equals(warzone.getWorld().getName()) && warzone.getVolume() != null && warzone.getVolume().contains(location)) {
        return warzone;
      }
    }
    return null;
  }

  public static Warzone getZoneByLocation(Player player) {
    return Warzone.getZoneByLocation(player.getLocation());
  }

  public static Warzone getZoneByPlayerName(String playerName) {
    for (Warzone warzone : War.war.getWarzones()) {
      Team team = warzone.getPlayerTeam(playerName);
      if (team != null) {
        return warzone;
      }
    }
    return null;
  }

  public static Warzone getZoneForDeadPlayer(Player player) {
    for (Warzone warzone : War.war.getWarzones()) {
      if (warzone.getReallyDeadFighters().contains(player.getName())) {
        return warzone;
      }
    }
    return null;
  }

  public boolean ready() {
    if (this.volume.hasTwoCorners() && !this.volume.tooSmall() && !this.volume.tooBig()) {
      return true;
    }
    return false;
  }

  public List<Team> getTeams() {
    return this.teams;
  }

  public Team getPlayerTeam(String playerName) {
    for (Team team : this.teams) {
      for (Player player : team.getPlayers()) {
        if (player.getName().equals(playerName)) {
          return team;
        }
      }
    }
    return null;
  }

  public String getTeamInformation() {
    StringBuilder teamsMessage = new StringBuilder(War.war.getString("zone.teaminfo.prefix"));
    if (this.getTeams().isEmpty()) {
      teamsMessage.append(War.war.getString("zone.teaminfo.none"));
    } else {
      for (Team team : this.getTeams()) {
        teamsMessage.append('\n');
        teamsMessage.append(MessageFormat.format(War.war.getString("zone.teaminfo.format"),
            team.getName(), team.getPoints(), team.getRemainingLifes(),
            team.getTeamConfig().resolveInt(TeamConfig.LIFEPOOL), StringUtils.join(team.getPlayerNames().iterator(), ", ")));
      }
    }
    return teamsMessage.toString();
  }

  public String getName() {
    return this.name;
  }

  @Override
  public String toString() {
    return this.getName();
  }

  public void setTeleport(Location location) {
    this.teleport = location;
  }

  public Location getTeleport() {
    return this.teleport;
  }

  public int saveState(boolean clearArtifacts) {
    if (this.ready()) {
      if (clearArtifacts) {
        // removed everything to keep save clean
        for (ZoneWallGuard guard : this.zoneWallGuards) {
          guard.deactivate();
        }
        this.zoneWallGuards.clear();

        for (Team team : this.teams) {
          for (Volume teamVolume : team.getSpawnVolumes().values()) {
            teamVolume.resetBlocks();
          }
          if (team.getTeamFlag() != null) {
            team.getFlagVolume().resetBlocks();
          }
        }

        for (Monument monument : this.monuments) {
          monument.getVolume().resetBlocks();
        }

        if (this.lobby != null) {
          this.lobby.getVolume().resetBlocks();
        }
      }

      this.volume.saveBlocks();
      if (clearArtifacts) {
        this.initializeZone(); // bring back stuff
      }
      return this.volume.size();
    }
    return 0;
  }

  /**
   * Goes back to the saved state of the warzone (resets only block types, not physics). Also teleports all players back to their respective spawns.
   *
   * @return
   */
  public void initializeZone() {
    this.initializeZone(null);
  }

  public void initializeZone(Player respawnExempted) {
    if (this.ready() && this.volume.isSaved()) {
      if (this.scoreboard != null) {
        for (OfflinePlayer opl : this.scoreboard.getPlayers()) {
          this.scoreboard.resetScores(opl);
        }
        this.scoreboard.clearSlot(DisplaySlot.SIDEBAR);
        for (Objective obj : this.scoreboard.getObjectives()) {
          obj.unregister();
        }
        for (Player player : Bukkit.getOnlinePlayers()) {
          if (player.getScoreboard() == this.scoreboard) {
            player.setScoreboard(Bukkit.getScoreboardManager().getMainScoreboard());
          }
        }
        this.scoreboard = null;
      }
      // everyone back to team spawn with full health
      for (Team team : this.teams) {
        for (Player player : team.getPlayers()) {
          if (player.equals(respawnExempted)) {
            continue;
          }
          if (this.getReallyDeadFighters().contains(player.getName())) {
            continue;
          }
          this.respawnPlayer(team, player);
        }
        team.setRemainingLives(team.getTeamConfig().resolveInt(TeamConfig.LIFEPOOL));
        team.initializeTeamSpawns();
        if (team.getTeamFlag() != null) {
          team.setTeamFlag(team.getTeamFlag());
        }
      }

      this.initZone();
     
      if (War.war.getWarHub() != null) {
        War.war.getWarHub().resetZoneSign(this);
      }
    }
   
    // Don't forget to reset these to false, or we won't be able to score or empty lifepools anymore
    this.isReinitializing = false;
    this.isEndOfGame = false;
  }

  public void initializeZoneAsJob(Player respawnExempted) {
    InitZoneJob job = new InitZoneJob(this, respawnExempted);
    War.war.getServer().getScheduler().scheduleSyncDelayedTask(War.war, job);
  }

  public void initializeZoneAsJob() {
    InitZoneJob job = new InitZoneJob(this);
    War.war.getServer().getScheduler().scheduleSyncDelayedTask(War.war, job);
  }

  private void initZone() {
    // reset monuments
    for (Monument monument : this.monuments) {
      monument.getVolume().resetBlocks();
      monument.addMonumentBlocks();
    }
   
    // reset bombs
    for (Bomb bomb : this.bombs) {
      bomb.getVolume().resetBlocks();
      bomb.addBombBlocks();
    }
   
    // reset cakes
    for (Cake cake : this.cakes) {
      cake.getVolume().resetBlocks();
      cake.addCakeBlocks();
    }

    // reset lobby (here be demons)
    if (this.lobby != null) {
      if (this.lobby.getVolume() != null) {
        this.lobby.getVolume().resetBlocks();
      }
      this.lobby.initialize();
    }

    this.flagThieves.clear();
    this.bombThieves.clear();
    this.cakeThieves.clear();
    if (this.getScoreboardType() != ScoreboardType.NONE) {
      this.scoreboard = Bukkit.getScoreboardManager().getNewScoreboard();
      scoreboard.registerNewObjective(this.getScoreboardType().getDisplayName(), "dummy");
      Objective obj = scoreboard.getObjective(this.getScoreboardType().getDisplayName());
      Validate.isTrue(obj.isModifiable(), "Cannot modify players' scores on the " + this.name + " scoreboard.");
      for (Team team : this.getTeams()) {
        String teamName = team.getKind().getColor() + team.getName() + ChatColor.RESET;
        if (this.getScoreboardType() == ScoreboardType.POINTS) {
          obj.getScore(Bukkit.getOfflinePlayer(teamName)).setScore(team.getPoints());
        } else if (this.getScoreboardType() == ScoreboardType.LIFEPOOL) {
          obj.getScore(Bukkit.getOfflinePlayer(teamName)).setScore(team.getRemainingLifes());
        }
      }
      obj.setDisplaySlot(DisplaySlot.SIDEBAR);
      for (Team team : this.getTeams()) {
        for (Player player : team.getPlayers()) {
          player.setScoreboard(scoreboard);
        }
      }
    }
    // nom drops
    for(Entity entity : (this.getWorld().getEntities())) {
      if (!(entity instanceof Item)) {
        continue;
      }
     
      // validate position
      if (!this.getVolume().contains(entity.getLocation())) {
        continue;
      }

      // omnomnomnom
      entity.remove();
    }
  }

  public void endRound() {

  }

  public void respawnPlayer(Team team, Player player) {
    this.handleRespawn(team, player);
    // Teleport the player back to spawn
    player.teleport(team.getRandomSpawn());
  }

  public void respawnPlayer(PlayerMoveEvent event, Team team, Player player) {
    this.handleRespawn(team, player);
    // Teleport the player back to spawn
    event.setTo(team.getRandomSpawn());
  }
 
  public boolean isRespawning(Player p) {
    return respawn.contains(p);
  }

  private void handleRespawn(final Team team, final Player player) {
    // Fill hp
    player.setRemainingAir(player.getMaximumAir());
    player.setHealth(player.getMaxHealth());
    player.setFoodLevel(20);
    player.setSaturation(team.getTeamConfig().resolveInt(TeamConfig.SATURATION));
    player.setExhaustion(0);
    player.setFallDistance(0);
    War.war.getServer().getScheduler().runTaskLater(War.war, new Runnable() {

      @Override
      public void run() {
        // Stop fire here, since doing it in the same tick as death doesn't extinguish it
        player.setFireTicks(0);
      }
     
    }, 1L);

    this.preventItemHackingThroughOpenedInventory(player);
   
    player.setLevel(0);
    player.setExp(0);
    player.setAllowFlight(false);
    player.setFlying(false);

    player.getInventory().clear();
   
    this.setKillCount(player.getName(), 0);

    if (player.getGameMode() != GameMode.SURVIVAL) {
      // Players are always in survival mode in warzones
      player.setGameMode(GameMode.SURVIVAL);
    }
   
    // clear potion effects
    PotionEffectHelper.clearPotionEffects(player);

    String potionEffect = team.getTeamConfig().resolveString(TeamConfig.APPLYPOTION);
    if (!potionEffect.isEmpty()) {
      PotionEffect effect = War.war.getPotionEffect(potionEffect);
      if (effect != null) {
        player.addPotionEffect(effect);
      } else {
        War.war.getLogger().log(Level.WARNING,
          "Failed to apply potion effect {0} in warzone {1}.",
          new Object[] {potionEffect, name});
      }
    }
   
    boolean isFirstRespawn = false;
    if (!this.getLoadoutSelections().keySet().contains(player.getName())) {
      isFirstRespawn = true;
      this.getLoadoutSelections().put(player.getName(), new LoadoutSelection(true, 0));
    } else if (this.isReinitializing) {
      isFirstRespawn = true;
      this.getLoadoutSelections().get(player.getName()).setStillInSpawn(true);
    } else {
      this.getLoadoutSelections().get(player.getName()).setStillInSpawn(true);
    }
   
    // Spout
    if (War.war.isSpoutServer()) {
      SpoutManager.getPlayer(player).setTitle(team.getKind().getColor() + player.getName());
    }

    War.war.getKillstreakReward().getAirstrikePlayers().remove(player.getName());

    final LoadoutResetJob job = new LoadoutResetJob(this, team, player, isFirstRespawn, false);
    if (team.getTeamConfig().resolveInt(TeamConfig.RESPAWNTIMER) == 0 || isFirstRespawn) {
      job.run();
    }     
    else {
      // "Respawn" Timer - player will not be able to leave spawn for a few seconds
      respawn.add(player);
     
      War.war.getServer().getScheduler().scheduleSyncDelayedTask(War.war, new Runnable() {
        public void run() {
            respawn.remove(player);
          War.war.getServer().getScheduler().scheduleSyncDelayedTask(War.war, job);
        }
      }, team.getTeamConfig().resolveInt(TeamConfig.RESPAWNTIMER) * 20L); // 20 ticks = 1 second
    }
  }

  public void resetInventory(Team team, Player player, Map<Integer, ItemStack> loadout) {
    // Reset inventory to loadout
    PlayerInventory playerInv = player.getInventory();
    playerInv.clear();
    playerInv.clear(playerInv.getSize() + 0);
    playerInv.clear(playerInv.getSize() + 1);
    playerInv.clear(playerInv.getSize() + 2);
    playerInv.clear(playerInv.getSize() + 3); // helmet/blockHead
    for (Integer slot : loadout.keySet()) {
      if (slot == 100) {
        playerInv.setBoots(loadout.get(slot).clone());
      } else if (slot == 101) {
        playerInv.setLeggings(loadout.get(slot).clone());
      } else if (slot == 102) {
        playerInv.setChestplate(loadout.get(slot).clone());
      } else if (slot == 103) {
        playerInv.setHelmet(loadout.get(slot).clone());
      } else {
        ItemStack item = loadout.get(slot);
        if (item != null) {
          playerInv.addItem(item.clone());
        }
      }
    }
    if (this.getWarzoneConfig().getBoolean(WarzoneConfig.BLOCKHEADS)) {
      playerInv.setHelmet(team.getKind().getHat());
    }
  }

  public boolean isMonumentCenterBlock(Block block) {
    for (Monument monument : this.monuments) {
      int x = monument.getLocation().getBlockX();
      int y = monument.getLocation().getBlockY() + 1;
      int z = monument.getLocation().getBlockZ();
      if (x == block.getX() && y == block.getY() && z == block.getZ()) {
        return true;
      }
    }
    return false;
  }

  public Monument getMonumentFromCenterBlock(Block block) {
    for (Monument monument : this.monuments) {
      int x = monument.getLocation().getBlockX();
      int y = monument.getLocation().getBlockY() + 1;
      int z = monument.getLocation().getBlockZ();
      if (x == block.getX() && y == block.getY() && z == block.getZ()) {
        return monument;
      }
    }
    return null;
  }

  public boolean nearAnyOwnedMonument(Location to, Team team) {
    for (Monument monument : this.monuments) {
      if (monument.isNear(to) && monument.isOwner(team)) {
        return true;
      }
    }
    return false;
  }

  public List<Monument> getMonuments() {
    return this.monuments;
  }

  public boolean hasPlayerState(String playerName) {
    return this.playerStates.containsKey(playerName);
  }

  public void keepPlayerState(Player player) {
    PlayerInventory inventory = player.getInventory();
    ItemStack[] contents = inventory.getContents();
   
    String playerTitle = player.getName();
    if (War.war.isSpoutServer()) {
      playerTitle = SpoutManager.getPlayer(player).getTitle();
    }
   
    this.playerStates.put(
        player.getName(),
        new PlayerState(player.getGameMode(), contents, inventory
            .getHelmet(), inventory.getChestplate(), inventory
            .getLeggings(), inventory.getBoots(), player
            .getHealth(), player.getExhaustion(), player
            .getSaturation(), player.getFoodLevel(), player
            .getActivePotionEffects(), playerTitle, player
            .getLevel(), player.getExp(), player.getAllowFlight()));
  }

  public void restorePlayerState(Player player) {
    PlayerState originalState = this.playerStates.remove(player.getName());
    PlayerInventory playerInv = player.getInventory();
    if (originalState != null) {
      // prevent item hacking thru CRAFTING personal inventory slots
      this.preventItemHackingThroughOpenedInventory(player);
     
      this.playerInvFromInventoryStash(playerInv, originalState);
      player.setGameMode(originalState.getGamemode());
      player.setHealth(Math.max(Math.min(originalState.getHealth(), 20.0D), 0.0D));
      player.setExhaustion(originalState.getExhaustion());
      player.setSaturation(originalState.getSaturation());
      player.setFoodLevel(originalState.getFoodLevel());
      PotionEffectHelper.restorePotionEffects(player, originalState.getPotionEffects());
      player.setLevel(originalState.getLevel());
      player.setExp(originalState.getExp());
      player.setAllowFlight(originalState.canFly());
     
      if (War.war.isSpoutServer()) {
        SpoutManager.getPlayer(player).setTitle(originalState.getPlayerTitle());
      }
    }
    player.setScoreboard(Bukkit.getScoreboardManager().getMainScoreboard());
  }

  private void preventItemHackingThroughOpenedInventory(Player player) {
    InventoryView openedInv = player.getOpenInventory();
    if (openedInv.getType() == InventoryType.CRAFTING) {
      // prevent abuse of personal crafting slots (this behavior doesn't seem to happen
      // for containers like workbench and furnace - those get closed properly)
      openedInv.getTopInventory().clear();
    }
   
    // Prevent player from keeping items he was transferring in his inventory
    openedInv.setCursor(null);
  }

  private void playerInvFromInventoryStash(PlayerInventory playerInv, PlayerState originalContents) {
    playerInv.clear();
   
    playerInv.clear(playerInv.getSize() + 0);
    playerInv.clear(playerInv.getSize() + 1);
    playerInv.clear(playerInv.getSize() + 2);
    playerInv.clear(playerInv.getSize() + 3); // helmet/blockHead
   
    int invIndex = 0;
    for (ItemStack item : originalContents.getContents()) {
      if (item != null && item.getType() != Material.AIR) {
        playerInv.setItem(invIndex, item);
      }
      invIndex++;
    }
   
    if (originalContents.getHelmet() != null) {
      playerInv.setHelmet(originalContents.getHelmet());
    }
    if (originalContents.getChest() != null) {
      playerInv.setChestplate(originalContents.getChest());
    }
    if (originalContents.getLegs() != null) {
      playerInv.setLeggings(originalContents.getLegs());
    }
    if (originalContents.getFeet() != null) {
      playerInv.setBoots(originalContents.getFeet());
    }
  }

  public boolean hasMonument(String monumentName) {
    for (Monument monument : this.monuments) {
      if (monument.getName().startsWith(monumentName)) {
        return true;
      }
    }
    return false;
  }

  public Monument getMonument(String monumentName) {
    for (Monument monument : this.monuments) {
      if (monument.getName().startsWith(monumentName)) {
        return monument;
      }
    }
    return null;
  }
 
  public boolean hasBomb(String bombName) {
    for (Bomb bomb : this.bombs) {
      if (bomb.getName().equals(bombName)) {
        return true;
      }
    }
    return false;
  }

  public Bomb getBomb(String bombName) {
    for (Bomb bomb : this.bombs) {
      if (bomb.getName().startsWith(bombName)) {
        return bomb;
      }
    }
    return null;
  }
 
  public boolean hasCake(String cakeName) {
    for (Cake cake : this.cakes) {
      if (cake.getName().equals(cakeName)) {
        return true;
      }
    }
    return false;
  }

  public Cake getCake(String cakeName) {
    for (Cake cake : this.cakes) {
      if (cake.getName().startsWith(cakeName)) {
        return cake;
      }
    }
    return null;
  }

  public boolean isImportantBlock(Block block) {
    if (this.ready()) {
      for (Monument m : this.monuments) {
        if (m.getVolume().contains(block)) {
          return true;
        }
      }
      for (Bomb b : this.bombs) {
        if (b.getVolume().contains(block)) {
          return true;
        }
      }
      for (Cake c : this.cakes) {
        if (c.getVolume().contains(block)) {
          return true;
        }
      }
      for (Team t : this.teams) {
        for (Volume tVolume : t.getSpawnVolumes().values()) {
          if (tVolume.contains(block)) {
            return true;
          }
        }
        if (t.getFlagVolume() != null && t.getFlagVolume().contains(block)) {
          return true;
        }
      }
      if (this.volume.isWallBlock(block)) {
        return true;
      }
    }
    return false;
  }

  public World getWorld() {

    return this.world;
  }

  public void setWorld(World world) {
    this.world = world;
  }

  public ZoneVolume getVolume() {
    return this.volume;
  }

  public void setVolume(ZoneVolume zoneVolume) {
    this.volume = zoneVolume;
  }

  public Team getTeamByKind(TeamKind kind) {
    for (Team t : this.teams) {
      if (t.getKind() == kind) {
        return t;
      }
    }
    return null;
  }

  public boolean isNearWall(Location latestPlayerLocation) {
    if (this.volume.hasTwoCorners()) {
      if (Math.abs(this.volume.getSoutheastZ() - latestPlayerLocation.getBlockZ()) < this.minSafeDistanceFromWall && latestPlayerLocation.getBlockX() <= this.volume.getSoutheastX() && latestPlayerLocation.getBlockX() >= this.volume.getNorthwestX() && latestPlayerLocation.getBlockY() >= this.volume.getMinY() && latestPlayerLocation.getBlockY() <= this.volume.getMaxY()) {
        return true; // near east wall
      } else if (Math.abs(this.volume.getSoutheastX() - latestPlayerLocation.getBlockX()) < this.minSafeDistanceFromWall && latestPlayerLocation.getBlockZ() <= this.volume.getNorthwestZ() && latestPlayerLocation.getBlockZ() >= this.volume.getSoutheastZ() && latestPlayerLocation.getBlockY() >= this.volume.getMinY() && latestPlayerLocation.getBlockY() <= this.volume.getMaxY()) {
        return true; // near south wall
      } else if (Math.abs(this.volume.getNorthwestX() - latestPlayerLocation.getBlockX()) < this.minSafeDistanceFromWall && latestPlayerLocation.getBlockZ() <= this.volume.getNorthwestZ() && latestPlayerLocation.getBlockZ() >= this.volume.getSoutheastZ() && latestPlayerLocation.getBlockY() >= this.volume.getMinY() && latestPlayerLocation.getBlockY() <= this.volume.getMaxY()) {
        return true; // near north wall
      } else if (Math.abs(this.volume.getNorthwestZ() - latestPlayerLocation.getBlockZ()) < this.minSafeDistanceFromWall && latestPlayerLocation.getBlockX() <= this.volume.getSoutheastX() && latestPlayerLocation.getBlockX() >= this.volume.getNorthwestX() && latestPlayerLocation.getBlockY() >= this.volume.getMinY() && latestPlayerLocation.getBlockY() <= this.volume.getMaxY()) {
        return true; // near west wall
      } else if (Math.abs(this.volume.getMaxY() - latestPlayerLocation.getBlockY()) < this.minSafeDistanceFromWall && latestPlayerLocation.getBlockX() <= this.volume.getMaxX() && latestPlayerLocation.getBlockX() >= this.volume.getMinX() && latestPlayerLocation.getBlockZ() <= this.volume.getMaxZ() && latestPlayerLocation.getBlockZ() >= this.volume.getMinZ()) {
        return true; // near up wall
      } else if (Math.abs(this.volume.getMinY() - latestPlayerLocation.getBlockY()) < this.minSafeDistanceFromWall && latestPlayerLocation.getBlockX() <= this.volume.getMaxX() && latestPlayerLocation.getBlockX() >= this.volume.getMinX() && latestPlayerLocation.getBlockZ() <= this.volume.getMaxZ() && latestPlayerLocation.getBlockZ() >= this.volume.getMinZ()) {
        return true; // near down wall
      }
    }
    return false;
  }

  public List<Block> getNearestWallBlocks(Location latestPlayerLocation) {
    List<Block> nearestWallBlocks = new ArrayList<Block>();
    if (Math.abs(this.volume.getSoutheastZ() - latestPlayerLocation.getBlockZ()) < this.minSafeDistanceFromWall && latestPlayerLocation.getBlockX() <= this.volume.getSoutheastX() && latestPlayerLocation.getBlockX() >= this.volume.getNorthwestX() && latestPlayerLocation.getBlockY() >= this.volume.getMinY() && latestPlayerLocation.getBlockY() <= this.volume.getMaxY()) {
      // near east wall
      Block eastWallBlock = this.world.getBlockAt(latestPlayerLocation.getBlockX() + 1, latestPlayerLocation.getBlockY() + 1, this.volume.getSoutheastZ());
      nearestWallBlocks.add(eastWallBlock);
    }

    if (Math.abs(this.volume.getSoutheastX() - latestPlayerLocation.getBlockX()) < this.minSafeDistanceFromWall && latestPlayerLocation.getBlockZ() <= this.volume.getNorthwestZ() && latestPlayerLocation.getBlockZ() >= this.volume.getSoutheastZ() && latestPlayerLocation.getBlockY() >= this.volume.getMinY() && latestPlayerLocation.getBlockY() <= this.volume.getMaxY()) {
      // near south wall
      Block southWallBlock = this.world.getBlockAt(this.volume.getSoutheastX(), latestPlayerLocation.getBlockY() + 1, latestPlayerLocation.getBlockZ());
      nearestWallBlocks.add(southWallBlock);
    }

    if (Math.abs(this.volume.getNorthwestX() - latestPlayerLocation.getBlockX()) < this.minSafeDistanceFromWall && latestPlayerLocation.getBlockZ() <= this.volume.getNorthwestZ() && latestPlayerLocation.getBlockZ() >= this.volume.getSoutheastZ() && latestPlayerLocation.getBlockY() >= this.volume.getMinY() && latestPlayerLocation.getBlockY() <= this.volume.getMaxY()) {
      // near north wall
      Block northWallBlock = this.world.getBlockAt(this.volume.getNorthwestX(), latestPlayerLocation.getBlockY() + 1, latestPlayerLocation.getBlockZ());
      nearestWallBlocks.add(northWallBlock);
    }

    if (Math.abs(this.volume.getNorthwestZ() - latestPlayerLocation.getBlockZ()) < this.minSafeDistanceFromWall && latestPlayerLocation.getBlockX() <= this.volume.getSoutheastX() && latestPlayerLocation.getBlockX() >= this.volume.getNorthwestX() && latestPlayerLocation.getBlockY() >= this.volume.getMinY() && latestPlayerLocation.getBlockY() <= this.volume.getMaxY()) {
      // near west wall
      Block westWallBlock = this.world.getBlockAt(latestPlayerLocation.getBlockX(), latestPlayerLocation.getBlockY() + 1, this.volume.getNorthwestZ());
      nearestWallBlocks.add(westWallBlock);
    }

    if (Math.abs(this.volume.getMaxY() - latestPlayerLocation.getBlockY()) < this.minSafeDistanceFromWall && latestPlayerLocation.getBlockX() <= this.volume.getMaxX() && latestPlayerLocation.getBlockX() >= this.volume.getMinX() && latestPlayerLocation.getBlockZ() <= this.volume.getMaxZ() && latestPlayerLocation.getBlockZ() >= this.volume.getMinZ()) {
      // near up wall
      Block upWallBlock = this.world.getBlockAt(latestPlayerLocation.getBlockX(), this.volume.getMaxY(), latestPlayerLocation.getBlockZ());
      nearestWallBlocks.add(upWallBlock);
    }

    if (Math.abs(this.volume.getMinY() - latestPlayerLocation.getBlockY()) < this.minSafeDistanceFromWall && latestPlayerLocation.getBlockX() <= this.volume.getMaxX() && latestPlayerLocation.getBlockX() >= this.volume.getMinX() && latestPlayerLocation.getBlockZ() <= this.volume.getMaxZ() && latestPlayerLocation.getBlockZ() >= this.volume.getMinZ()) {
      // near down wall
      Block downWallBlock = this.world.getBlockAt(latestPlayerLocation.getBlockX(), this.volume.getMinY(), latestPlayerLocation.getBlockZ());
      nearestWallBlocks.add(downWallBlock);
    }
    return nearestWallBlocks;
    // note: y + 1 to line up 3 sided square with player eyes
  }

  public List<BlockFace> getNearestWalls(Location latestPlayerLocation) {
    List<BlockFace> walls = new ArrayList<BlockFace>();
    if (Math.abs(this.volume.getSoutheastZ() - latestPlayerLocation.getBlockZ()) < this.minSafeDistanceFromWall && latestPlayerLocation.getBlockX() <= this.volume.getSoutheastX() && latestPlayerLocation.getBlockX() >= this.volume.getNorthwestX() && latestPlayerLocation.getBlockY() >= this.volume.getMinY() && latestPlayerLocation.getBlockY() <= this.volume.getMaxY()) {
      // near east wall
      walls.add(Direction.EAST());
    }

    if (Math.abs(this.volume.getSoutheastX() - latestPlayerLocation.getBlockX()) < this.minSafeDistanceFromWall && latestPlayerLocation.getBlockZ() <= this.volume.getNorthwestZ() && latestPlayerLocation.getBlockZ() >= this.volume.getSoutheastZ() && latestPlayerLocation.getBlockY() >= this.volume.getMinY() && latestPlayerLocation.getBlockY() <= this.volume.getMaxY()) {
      // near south wall
      walls.add(Direction.SOUTH());
    }

    if (Math.abs(this.volume.getNorthwestX() - latestPlayerLocation.getBlockX()) < this.minSafeDistanceFromWall && latestPlayerLocation.getBlockZ() <= this.volume.getNorthwestZ() && latestPlayerLocation.getBlockZ() >= this.volume.getSoutheastZ() && latestPlayerLocation.getBlockY() >= this.volume.getMinY() && latestPlayerLocation.getBlockY() <= this.volume.getMaxY()) {
      // near north wall
      walls.add(Direction.NORTH());
    }

    if (Math.abs(this.volume.getNorthwestZ() - latestPlayerLocation.getBlockZ()) < this.minSafeDistanceFromWall && latestPlayerLocation.getBlockX() <= this.volume.getSoutheastX() && latestPlayerLocation.getBlockX() >= this.volume.getNorthwestX() && latestPlayerLocation.getBlockY() >= this.volume.getMinY() && latestPlayerLocation.getBlockY() <= this.volume.getMaxY()) {
      // near west wall
      walls.add(Direction.WEST());
    }

    if (Math.abs(this.volume.getMaxY() - latestPlayerLocation.getBlockY()) < this.minSafeDistanceFromWall && latestPlayerLocation.getBlockX() <= this.volume.getMaxX() && latestPlayerLocation.getBlockX() >= this.volume.getMinX() && latestPlayerLocation.getBlockZ() <= this.volume.getMaxZ() && latestPlayerLocation.getBlockZ() >= this.volume.getMinZ()) {
      // near up wall
      walls.add(BlockFace.UP);
    }

    if (Math.abs(this.volume.getMinY() - latestPlayerLocation.getBlockY()) < this.minSafeDistanceFromWall && latestPlayerLocation.getBlockX() <= this.volume.getMaxX() && latestPlayerLocation.getBlockX() >= this.volume.getMinX() && latestPlayerLocation.getBlockZ() <= this.volume.getMaxZ() && latestPlayerLocation.getBlockZ() >= this.volume.getMinZ()) {
      // near down wall
      walls.add(BlockFace.DOWN);
    }
    return walls;
  }

  public ZoneWallGuard getPlayerZoneWallGuard(String name, BlockFace wall) {
    for (ZoneWallGuard guard : this.zoneWallGuards) {
      if (guard.getPlayer().getName().equals(name) && wall == guard.getWall()) {
        return guard;
      }
    }
    return null;
  }

  public boolean protectZoneWallAgainstPlayer(Player player) {
    List<BlockFace> nearestWalls = this.getNearestWalls(player.getLocation());
    boolean protecting = false;
    for (BlockFace wall : nearestWalls) {
      ZoneWallGuard guard = this.getPlayerZoneWallGuard(player.getName(), wall);
      if (guard != null) {
        // already protected, need to move the guard
        guard.updatePlayerPosition(player.getLocation());
      } else {
        // new guard
        guard = new ZoneWallGuard(player, War.war, this, wall);
        this.zoneWallGuards.add(guard);
      }
      protecting = true;
    }
    return protecting;
  }

  public void dropZoneWallGuardIfAny(Player player) {
    List<ZoneWallGuard> playerGuards = new ArrayList<ZoneWallGuard>();
    for (ZoneWallGuard guard : this.zoneWallGuards) {
      if (guard.getPlayer().getName().equals(player.getName())) {
        playerGuards.add(guard);
        guard.deactivate();
      }
    }
    // now remove those zone guards
    for (ZoneWallGuard playerGuard : playerGuards) {
      this.zoneWallGuards.remove(playerGuard);
    }
    playerGuards.clear();
  }

  public void setLobby(ZoneLobby lobby) {
    this.lobby = lobby;
  }

  public ZoneLobby getLobby() {
    return this.lobby;
  }

  static final Comparator<Team> LEAST_PLAYER_COUNT_ORDER = new Comparator<Team>() {
    @Override
    public int compare(Team arg0, Team arg1) {
      return arg0.getPlayers().size() - arg1.getPlayers().size();
    }
  };

  public Team autoAssign(Player player) {
    Collections.sort(teams, LEAST_PLAYER_COUNT_ORDER);
    Team lowestNoOfPlayers = null;
    for (Team team : this.teams) {
      if (War.war.canPlayWar(player, team)) {
        lowestNoOfPlayers = team;
        break;
      }
    }
    if (lowestNoOfPlayers != null) {
      this.assign(player, lowestNoOfPlayers);
    }
    return lowestNoOfPlayers;
  }

  /**
   * Assign a player to a specific team.
   *
   * @param player
   *            Player to assign to team.
   * @param team
   *            Team to add the player to.
   * @return false if player does not have permission to join this team.
   */
  public boolean assign(Player player, Team team) {
    if (!War.war.canPlayWar(player, team)) {
      War.war.badMsg(player, "join.permission.single");
      return false;
    }
    if (player.getWorld() != this.getWorld()) {
      player.teleport(this.getWorld().getSpawnLocation());
    }
    team.addPlayer(player);
    team.resetSign();
    if (this.hasPlayerState(player.getName())) {
      War.war.getLogger().log(Level.WARNING, "Player {0} in warzone {1} already has a stored state - they may have lost items",
          new Object[] {player.getName(), this.getName()});
      this.playerStates.remove(player.getName());
    }
    this.getReallyDeadFighters().remove(player.getName());
    this.keepPlayerState(player);
    War.war.msg(player, "join.inventorystored");
    this.respawnPlayer(team, player);
    this.broadcast("join.broadcast", player.getName(), team.getKind().getFormattedName());
    return true;
  }
 
  private void dropItems(Location location, ItemStack[] items) {
    for (ItemStack item : items) {
      if (item == null || item.getType() == Material.AIR) {
        continue;
      }
      location.getWorld().dropItem(location, item);
    }
  }
 
  private Random killSeed = new Random();
 
  /**
   * Send death messages and process other records before passing off the
   * death to the {@link #handleDeath(Player)} method.
   * @param attacker Player who killed the defender
   * @param defender Player who was killed
   * @param damager Entity who caused the damage. Usually an arrow. Used for
   * specific death messages. Can be null.
   */
  public void handleKill(Player attacker, Player defender, Entity damager) {
    Team attackerTeam = this.getPlayerTeam(attacker.getName());
    Team defenderTeam = this.getPlayerTeam(defender.getName());
    if (this.getWarzoneConfig().getBoolean(WarzoneConfig.DEATHMESSAGES)) {
      String attackerString = attackerTeam.getKind().getColor() + attacker.getName();
      String defenderString = defenderTeam.getKind().getColor() + defender.getName();
      Material killerWeapon = attacker.getItemInHand().getType();
      String weaponString = killerWeapon.toString();
      if (attacker.getItemInHand().hasItemMeta() && attacker.getItemInHand().getItemMeta().hasDisplayName()) {
        weaponString = attacker.getItemInHand().getItemMeta().getDisplayName() + ChatColor.WHITE;
      }
      if (killerWeapon == Material.AIR) {
        weaponString = War.war.getString("pvp.kill.weapon.hand");
      } else if (killerWeapon == Material.BOW || damager instanceof Arrow) {
        int rand = killSeed.nextInt(3);
        if (rand == 0) {
          weaponString = War.war.getString("pvp.kill.weapon.bow");
        } else {
          weaponString = War.war.getString("pvp.kill.weapon.aim");
        }
      } else if (damager instanceof Projectile) {
        weaponString = War.war.getString("pvp.kill.weapon.aim");
      }
      String adjectiveString = War.war.getDeadlyAdjectives().isEmpty() ? "" : War.war.getDeadlyAdjectives().get(this.killSeed.nextInt(War.war.getDeadlyAdjectives().size()));
      String verbString = War.war.getKillerVerbs().isEmpty() ? "" : War.war.getKillerVerbs().get(this.killSeed.nextInt(War.war.getKillerVerbs().size()));
      this.broadcast("pvp.kill.format", attackerString + ChatColor.WHITE, adjectiveString,
          weaponString.toLowerCase().replace('_', ' '), verbString, defenderString);
    }
    this.addKillCount(attacker.getName(), 1);
    this.addKillDeathRecord(attacker, 1, 0);
    this.addKillDeathRecord(defender, 0, 1);
    if (attackerTeam.getTeamConfig().resolveBoolean(TeamConfig.XPKILLMETER)) {
      attacker.setLevel(this.getKillCount(attacker.getName()));
    }
    if (attackerTeam.getTeamConfig().resolveBoolean(TeamConfig.KILLSTREAK)) {
      War.war.getKillstreakReward().rewardPlayer(attacker, this.getKillCount(attacker.getName()));
    }
    if (this.getScoreboard() != null && this.getScoreboardType() == ScoreboardType.TOPKILLS) {
      Objective obj = this.getScoreboard().getObjective("Top kills");
      obj.getScore(attacker).setScore(this.getKillCount(attacker.getName()));
    }
    if (defenderTeam.getTeamConfig().resolveBoolean(TeamConfig.INVENTORYDROP)) {
      dropItems(defender.getLocation(), defender.getInventory().getContents());
      dropItems(defender.getLocation(), defender.getInventory().getArmorContents());
    }
    this.handleDeath(defender);
  }
 
  /**
   * Handle death messages before passing to {@link #handleDeath(Player)}
   * for post-processing. It's like
   * {@link #handleKill(Player, Player, Entity)}, but only for suicides.
   * @param player Player who killed himself
   */
  public void handleSuicide(Player player) {
    if (this.getWarzoneConfig().getBoolean(WarzoneConfig.DEATHMESSAGES)) {
      String defenderString = this.getPlayerTeam(player.getName()).getKind().getColor() + player.getName() + ChatColor.WHITE;
      this.broadcast("pvp.kill.self", defenderString);
    }
    this.handleDeath(player);
  }
 
  /**
   * Handle a player killed naturally (like by a dispenser or explosion).
   * @param player Player killed
   * @param event Event causing damage
   */
  public void handleNaturalKill(Player player, EntityDamageEvent event) {
    if (this.getWarzoneConfig().getBoolean(WarzoneConfig.DEATHMESSAGES)) {
      String defenderString = this.getPlayerTeam(player.getName()).getKind().getColor() + player.getName() + ChatColor.WHITE;
      if (event instanceof EntityDamageByEntityEvent
          && ((EntityDamageByEntityEvent) event).getDamager() instanceof TNTPrimed) {
        this.broadcast("pvp.death.explosion", defenderString + ChatColor.WHITE);
      } else if (event.getCause() == DamageCause.FIRE || event.getCause() == DamageCause.FIRE_TICK
          || event.getCause() == DamageCause.LAVA || event.getCause() == DamageCause.LIGHTNING) {
        this.broadcast("pvp.death.fire", defenderString);
      } else if (event.getCause() == DamageCause.DROWNING) {
        this.broadcast("pvp.death.drown", defenderString);
      } else if (event.getCause() == DamageCause.FALL) {
        this.broadcast("pvp.death.fall", defenderString);
      } else {
        this.broadcast("pvp.death.other", defenderString);
      }
    }
    this.handleDeath(player);
  }

  /**
   * Cleanup after a player who has died. This decrements the team's
   * remaining lifepool, drops stolen flags, and respawns the player.
   * It also handles team lose and score cap conditions.
   * This method is synchronized to prevent concurrent battle resets.
   * @param player Player who died
   */
  public synchronized void handleDeath(Player player) {
    Team playerTeam = this.getPlayerTeam(player.getName());
    Validate.notNull(playerTeam, "Can't find team for dead player " + player.getName());
    if (this.getWarzoneConfig().getBoolean(WarzoneConfig.REALDEATHS)) {
      this.getReallyDeadFighters().add(player.getName());
    } else {
      this.respawnPlayer(playerTeam, player);
    }
    if (playerTeam.getRemainingLifes() <= 0) {
      handleTeamLoss(playerTeam, player);
    } else {
      this.dropAllStolenObjects(player, false);
      playerTeam.setRemainingLives(playerTeam.getRemainingLifes() - 1);
      // Lifepool empty warning
      if (playerTeam.getRemainingLifes() - 1 == 0) {
        this.broadcast("zone.lifepool.empty", playerTeam.getName());
      }
    }
    playerTeam.resetSign();
  }
 
  private void handleTeamLoss(Team losingTeam, Player player) {
    StringBuilder teamScores = new StringBuilder();
    List<Team> winningTeams = new ArrayList<Team>(teams.size());
    for (Team team : this.teams) {
      if (team.getPlayers().isEmpty())
        continue;
      if (team != losingTeam) {
        team.addPoint();
        team.resetSign();
        winningTeams.add(team);
      }
      teamScores.append(String.format("\n%s (%d/%d) ", team.getName(), team.getPoints(), team.getTeamConfig().resolveInt(TeamConfig.MAXSCORE)));
      team.sendAchievement("Round over! " + losingTeam.getKind().getFormattedName(), "ran out of lives.", losingTeam.getKind().getBlockHead(), 10000);
    }
    this.broadcast("zone.battle.end", losingTeam.getName(), player.getName());
    WarBattleWinEvent event1 = new WarBattleWinEvent(this, winningTeams);
    War.war.getServer().getPluginManager().callEvent(event1);
    if (!teamScores.toString().isEmpty()) {
      this.broadcast("zone.battle.newscores", teamScores.toString());
    }
    if (War.war.getMysqlConfig().isEnabled() && War.war.getMysqlConfig().isLoggingEnabled()) {
      LogKillsDeathsJob logKillsDeathsJob = new LogKillsDeathsJob(ImmutableList.copyOf(this.getKillsDeathsTracker()));
      War.war.getServer().getScheduler().runTaskAsynchronously(War.war, logKillsDeathsJob);
    }
    this.getKillsDeathsTracker().clear();
    if (!detectScoreCap()) {
      this.broadcast("zone.battle.reset");
      if (this.getWarzoneConfig().getBoolean(WarzoneConfig.RESETBLOCKS)) {
        this.reinitialize();
      } else {
        this.initializeZone();
      }
    }
  }

  /**
   * Check if a team has achieved max score "score cap".
   * @return true if team has achieved max score, false otherwise.
   */
  public boolean detectScoreCap() {
    StringBuilder winnersStr = new StringBuilder();
    for (Team team : this.teams) {
      if (team.getPoints() >= team.getTeamConfig().resolveInt(TeamConfig.MAXSCORE)) {
        winnersStr.append(team.getName()).append(' ');
      }
    }
    if (!winnersStr.toString().isEmpty())
      this.handleScoreCapReached(winnersStr.toString());
    return !winnersStr.toString().isEmpty();
  }

  public void reinitialize() {
    this.isReinitializing = true;
    this.getVolume().resetBlocksAsJob();
  }

  public void handlePlayerLeave(Player player, Location destination, PlayerMoveEvent event, boolean removeFromTeam) {
    this.handlePlayerLeave(player);
    event.setTo(destination);
  }

  public void handlePlayerLeave(Player player, Location destination, boolean removeFromTeam) {
    this.handlePlayerLeave(player);
    player.teleport(destination);
  }

  private void handlePlayerLeave(Player player) {
    Team playerTeam = Team.getTeamByPlayerName(player.getName());
    if (playerTeam != null) {
      playerTeam.removePlayer(player);
      this.broadcast("leave.broadcast", playerTeam.getKind().getColor() + player.getName() + ChatColor.WHITE);
      playerTeam.resetSign();
      if (this.getPlayerCount() == 0 && this.getWarzoneConfig().getBoolean(WarzoneConfig.RESETONEMPTY)) {
        // reset the zone for a new game when the last player leaves
        for (Team team : this.getTeams()) {
          team.resetPoints();
          team.setRemainingLives(team.getTeamConfig().resolveInt(TeamConfig.LIFEPOOL));
        }
        if (!this.isReinitializing()) {
          this.reinitialize();
          War.war.getLogger().log(Level.INFO, "Last player left warzone {0}. Warzone blocks resetting automatically...", new Object[] {this.getName()});
        }
      }
     
      WarPlayerLeaveEvent event1 = new WarPlayerLeaveEvent(player.getName());
      War.war.getServer().getPluginManager().callEvent(event1);
    }
  }

  public boolean isEnemyTeamFlagBlock(Team playerTeam, Block block) {
    for (Team team : this.teams) {
      if (!team.getName().equals(playerTeam.getName()) && team.isTeamFlagBlock(block)) {
        return true;
      }
    }
    return false;
  }
 
  public boolean isFlagBlock(Block block) {
    for (Team team : this.teams) {
      if (team.isTeamFlagBlock(block)) {
        return true;
      }
    }
    return false;
  }

  public Team getTeamForFlagBlock(Block block) {
    for (Team team : this.teams) {
      if (team.isTeamFlagBlock(block)) {
        return team;
      }
    }
    return null;
  }
 
  public boolean isBombBlock(Block block) {
    for (Bomb bomb : this.bombs) {
      if (bomb.isBombBlock(block.getLocation())) {
        return true;
      }
    }
    return false;
  }

  public Bomb getBombForBlock(Block block) {
    for (Bomb bomb : this.bombs) {
      if (bomb.isBombBlock(block.getLocation())) {
        return bomb;
      }
    }
    return null;
  }
 
  public boolean isCakeBlock(Block block) {
    for (Cake cake : this.cakes) {
      if (cake.isCakeBlock(block.getLocation())) {
        return true;
      }
    }
    return false;
  }

  public Cake getCakeForBlock(Block block) {
    for (Cake cake : this.cakes) {
      if (cake.isCakeBlock(block.getLocation())) {
        return cake;
      }
    }
    return null;
  }

  // Flags
  public void addFlagThief(Team lostFlagTeam, String flagThief) {
    this.flagThieves.put(flagThief, lostFlagTeam);
    WarPlayerThiefEvent event1 = new WarPlayerThiefEvent(Bukkit.getPlayerExact(flagThief), WarPlayerThiefEvent.StolenObject.FLAG);
    War.war.getServer().getPluginManager().callEvent(event1);
  }

  public boolean isFlagThief(String suspect) {
    if (this.flagThieves.containsKey(suspect)) {
      return true;
    }
    return false;
  }

  public Team getVictimTeamForFlagThief(String thief) {
    return this.flagThieves.get(thief);
  }

  public void removeFlagThief(String thief) {
    this.flagThieves.remove(thief);
  }

  // Bomb
  public void addBombThief(Bomb bomb, String bombThief) {
    this.bombThieves.put(bombThief, bomb);
    WarPlayerThiefEvent event1 = new WarPlayerThiefEvent(Bukkit.getPlayerExact(bombThief), WarPlayerThiefEvent.StolenObject.BOMB);
    War.war.getServer().getPluginManager().callEvent(event1);
  }

  public boolean isBombThief(String suspect) {
    if (this.bombThieves.containsKey(suspect)) {
      return true;
    }
    return false;
  }

  public Bomb getBombForThief(String thief) {
    return this.bombThieves.get(thief);
  }

  public void removeBombThief(String thief) {
    this.bombThieves.remove(thief);
  }
 
  // Cake
 
  public void addCakeThief(Cake cake, String cakeThief) {
    this.cakeThieves.put(cakeThief, cake);
    WarPlayerThiefEvent event1 = new WarPlayerThiefEvent(Bukkit.getPlayerExact(cakeThief), WarPlayerThiefEvent.StolenObject.CAKE);
    War.war.getServer().getPluginManager().callEvent(event1);
  }

  public boolean isCakeThief(String suspect) {
    if (this.cakeThieves.containsKey(suspect)) {
      return true;
    }
    return false;
  }

  public Cake getCakeForThief(String thief) {
    return this.cakeThieves.get(thief);
  }

  public void removeCakeThief(String thief) {
    this.cakeThieves.remove(thief);
  }

  public void clearThieves() {
    this.flagThieves.clear();
    this.bombThieves.clear();
    this.cakeThieves.clear();
  }

  public boolean isTeamFlagStolen(Team team) {
    for (String playerKey : this.flagThieves.keySet()) {
      if (this.flagThieves.get(playerKey).getName().equals(team.getName())) {
        return true;
      }
    }
    return false;
  }
 
  public void handleScoreCapReached(String winnersStr) {
    // Score cap reached. Reset everything.
    this.isEndOfGame = true;
    List<Team> winningTeams = new ArrayList<Team>(teams.size());
    for (String team : winnersStr.split(" ")) {
      winningTeams.add(this.getTeamByKind(TeamKind.getTeam(team)));
    }
    WarScoreCapEvent event1 = new WarScoreCapEvent(winningTeams);
    War.war.getServer().getPluginManager().callEvent(event1);
   
    for (Team t : this.getTeams()) {
      if (War.war.isSpoutServer()) {
        for (Player p : t.getPlayers()) {
          SpoutPlayer sp = SpoutManager.getPlayer(p);
          if (sp.isSpoutCraftEnabled()) {
                    sp.sendNotification(
                        SpoutDisplayer.cleanForNotification("Match won! " + ChatColor.WHITE + "Winners:"),
                        SpoutDisplayer.cleanForNotification(SpoutDisplayer.addMissingColor(winnersStr, this)),
                        Material.CAKE,
                        (short)0,
                        10000);
          }
        }
      }
      String winnersStrAndExtra = "Score cap reached. Game is over! Winning team(s): " + winnersStr;
      winnersStrAndExtra += ". Resetting warzone and your inventory...";
      t.teamcast(winnersStrAndExtra);
      double ecoReward = t.getTeamConfig().resolveDouble(TeamConfig.ECOREWARD);
      boolean doEcoReward = ecoReward != 0 && War.war.getEconomy() != null;
      for (Iterator<Player> it = t.getPlayers().iterator(); it.hasNext();) {
        Player tp = it.next();
        it.remove(); // Remove player from team first to prevent anti-tp
        t.removePlayer(tp);
        tp.teleport(this.getEndTeleport(LeaveCause.SCORECAP));
        if (winnersStr.contains(t.getName())) {
          // give reward
          rewardPlayer(tp, t.getInventories().resolveReward());
          if (doEcoReward) {
            EconomyResponse r;
            if (ecoReward > 0) {
              r = War.war.getEconomy().depositPlayer(tp.getName(), ecoReward);
            } else {
              r = War.war.getEconomy().withdrawPlayer(tp.getName(), ecoReward);
            }
            if (!r.transactionSuccess()) {
              War.war.getLogger().log(Level.WARNING,
                "Failed to reward player {0} ${1}. Error: {2}",
                new Object[] {tp.getName(), ecoReward, r.errorMessage});
            }
          }
        }
      }
      t.resetPoints();
      t.getPlayers().clear(); // empty the team
      t.resetSign();
    }
    if (this.getWarzoneConfig().getBoolean(WarzoneConfig.RESETBLOCKS)) {
      this.reinitialize();
    } else {
      this.initializeZone();
    }
  }

  public void rewardPlayer(Player player, Map<Integer, ItemStack> reward) {
    for (Integer slot : reward.keySet()) {
      ItemStack item = reward.get(slot);
      if (item != null) {
        player.getInventory().addItem(item);
      }
    }
  }

  public boolean isDeadMan(String playerName) {
    if (this.deadMenInventories.containsKey(playerName)) {
      return true;
    }
    return false;
  }

  public void restoreDeadmanInventory(Player player) {
    if (this.isDeadMan(player.getName())) {
      this.playerInvFromInventoryStash(player.getInventory(), this.deadMenInventories.get(player.getName()));
      this.deadMenInventories.remove(player.getName());
    }
  }

  public void setRallyPoint(Location location) {
    this.rallyPoint = location;
  }

  public Location getRallyPoint() {
    return this.rallyPoint;
  }

  public void unload() {
    War.war.log("Unloading zone " + this.getName() + "...", Level.INFO);
    for (Team team : this.getTeams()) {
      for (Iterator<Player> it = team.getPlayers().iterator(); it.hasNext(); ) {
        final Player player = it.next();
        it.remove();
        team.removePlayer(player);
        player.teleport(this.getTeleport());
      }
    }
    if (this.getLobby() != null) {
      this.getLobby().getVolume().resetBlocks();
    }
    if (this.getWarzoneConfig().getBoolean(WarzoneConfig.RESETONUNLOAD)) {
      this.getVolume().resetBlocks();
    }
  }

  public boolean isEnoughPlayers() {
    int teamsWithEnough = 0;
    for (Team team : teams) {
      if (team.getPlayers().size() >= this.getWarzoneConfig().getInt(WarzoneConfig.MINPLAYERS)) {
        teamsWithEnough++;
      }
    }
    if (teamsWithEnough >= this.getWarzoneConfig().getInt(WarzoneConfig.MINTEAMS)) {
      return true;
    }
    return false;
  }

  public HashMap<String, LoadoutSelection> getLoadoutSelections() {
    return loadoutSelections;
  }

  public boolean isAuthor(Player player) {
    // if no authors, all zonemakers can edit the zone
    return authors.size() == 0 || authors.contains(player.getName());
  }
   
  public void addAuthor(String playerName) {
    authors.add(playerName);
  }
 
  public List<String> getAuthors() {
    return this.authors;
  }

  public String getAuthorsString() {
    String authors = "";
    for (String author : this.getAuthors()) {
      authors += author + ",";
    }
    return authors;
  }

  public void equipPlayerLoadoutSelection(Player player, Team playerTeam, boolean isFirstRespawn, boolean isToggle) {
    LoadoutSelection selection = this.getLoadoutSelections().get(player.getName());
    if (selection != null && !this.isRespawning(player) && playerTeam.getPlayers().contains(player)) {
      // Make sure that inventory resets dont occur if player has already tp'ed out (due to game end, or somesuch)
      // - repawn timer + this method is why inventories were getting wiped as players exited the warzone.
      List<Loadout> loadouts = playerTeam.getInventories().resolveNewLoadouts();
      List<String> sortedNames = LoadoutYmlMapper.sortNames(Loadout.toLegacyFormat(loadouts));
      sortedNames.remove("first");
      for (Iterator<String> it = sortedNames.iterator(); it.hasNext();) {
        String loadoutName = it.next();
        Loadout ldt = Loadout.getLoadout(loadouts, loadoutName);
        if (ldt.requiresPermission() && !player.hasPermission(ldt.getPermission())) {
          it.remove();
        }
      }
      if (sortedNames.isEmpty()) {
        // Fix for zones that mistakenly only specify a `first' loadout, but do not add any others.
        this.resetInventory(playerTeam, player, Collections.<Integer, ItemStack>emptyMap());
        War.war.msg(player, "404 No loadouts found");
        return;
      }
      int currentIndex = selection.getSelectedIndex();
      Loadout firstLoadout = Loadout.getLoadout(loadouts, "first");
      int i = 0;
      Iterator<String> it = sortedNames.iterator();
      while (it.hasNext()) {
        String name = (String) it.next();
        if (i == currentIndex) {
          if (playerTeam.getTeamConfig().resolveBoolean(TeamConfig.PLAYERLOADOUTASDEFAULT) && name.equals("default")) {
            // Use player's own inventory as loadout
            this.resetInventory(playerTeam, player, this.getPlayerInventoryFromSavedState(player));
          } else if (isFirstRespawn && firstLoadout != null && name.equals("default")
              && (firstLoadout.requiresPermission() ? player.hasPermission(firstLoadout.getPermission()) : true)) {
            // Get the loadout for the first spawn
            this.resetInventory(playerTeam, player, Loadout.getLoadout(loadouts, "first").getContents());
          } else {
            // Use the loadout from the list in the settings
            this.resetInventory(playerTeam, player, Loadout.getLoadout(loadouts, name).getContents());
          }
          if (isFirstRespawn && playerTeam.getInventories().resolveLoadouts().keySet().size() > 1 || isToggle) {
            War.war.msg(player, "zone.loadout.equip", name);
          }
        }
        i++;
      }
    }
  }

  private HashMap<Integer, ItemStack> getPlayerInventoryFromSavedState(Player player) {
    HashMap<Integer, ItemStack> playerItems = new HashMap<Integer, ItemStack>();
    PlayerState originalState = this.playerStates.get(player.getName());

    if (originalState != null) {
      int invIndex = 0;
      playerItems = new HashMap<Integer, ItemStack>();
      for (ItemStack item : originalState.getContents()) {
        if (item != null && item.getType() != Material.AIR) {
          playerItems.put(invIndex, item);
        }
        invIndex++;
      }
      if (originalState.getFeet() != null) {
        playerItems.put(100, originalState.getFeet());
      }
      if (originalState.getLegs() != null) {
        playerItems.put(101, originalState.getLegs());
      }
      if (originalState.getChest() != null) {
        playerItems.put(102, originalState.getChest());
      }
      if (originalState.getHelmet() != null) {
        playerItems.put(103, originalState.getHelmet());
      }
     
      if (War.war.isSpoutServer()) {
        SpoutManager.getPlayer(player).setTitle(originalState.getPlayerTitle());
      }
    }
   
    return playerItems;
  }

  public WarzoneConfigBag getWarzoneConfig() {
    return this.warzoneConfig;
  }
 
  public TeamConfigBag getTeamDefaultConfig() {
    return this.teamDefaultConfig;
  }

  public InventoryBag getDefaultInventories() {
    return this.defaultInventories ;
  }

  public List<Bomb> getBombs() {
    return bombs;
  }

  public List<Cake> getCakes() {
    return cakes;
  }

  public List<String> getReallyDeadFighters() {
    return this.reallyDeadFighters ;
  }

  public boolean isEndOfGame() {
    return this.isEndOfGame;
  }

  public boolean isReinitializing() {
    return this.isReinitializing;
  }

//  public Object getGameEndLock() {
//    return gameEndLock;
//  }

  public void setName(String newName) {
    this.name = newName;
    this.volume.setName(newName);
  }

  public HubLobbyMaterials getLobbyMaterials() {
    return this.lobbyMaterials;
  }

  public void setLobbyMaterials(HubLobbyMaterials lobbyMaterials) {
    this.lobbyMaterials = lobbyMaterials;
  }

  public boolean isOpponentSpawnPeripheryBlock(Team team, Block block) {
    for (Team maybeOpponent : this.getTeams()) {
      if (maybeOpponent != team) {
        for (Volume teamSpawnVolume : maybeOpponent.getSpawnVolumes().values()) {
          Volume periphery = new Volume(new Location(
              teamSpawnVolume.getWorld(),
              teamSpawnVolume.getMinX() - 1,
              teamSpawnVolume.getMinY() - 1,
              teamSpawnVolume.getMinZ() - 1), new Location(
              teamSpawnVolume.getWorld(),
              teamSpawnVolume.getMaxX() + 1,
              teamSpawnVolume.getMaxY() + 1,
              teamSpawnVolume.getMaxZ() + 1));
          if (periphery.contains(block)) {
            return true;
          }
        }
      }
    }
    return false;
  }

  public void setWarzoneMaterials(WarzoneMaterials warzoneMaterials) {
    this.warzoneMaterials = warzoneMaterials;
  }

  public WarzoneMaterials getWarzoneMaterials() {
    return warzoneMaterials;
  }

  public Scoreboard getScoreboard() {
    return scoreboard;
  }

  public ScoreboardType getScoreboardType() {
    return this.getWarzoneConfig().getScoreboardType(WarzoneConfig.SCOREBOARD);
  }
  public boolean hasKillCount(String player) {
    return killCount.containsKey(player);
  }

  public int getKillCount(String player) {
    return killCount.get(player);
  }

  public void setKillCount(String player, int totalKills) {
    if (totalKills < 0) {
      throw new IllegalArgumentException("Amount of kills to set cannot be a negative number.");
    }
    killCount.put(player, totalKills);
  }

  public void addKillCount(String player, int amount) {
    if (amount < 0) {
      throw new IllegalArgumentException("Amount of kills to add cannot be a negative number.");
    }
    killCount.put(player, killCount.get(player) + amount);
  }

  public void addKillDeathRecord(OfflinePlayer player, int kills, int deaths) {
    for (Iterator<KillsDeathsRecord> it = this.killsDeathsTracker.iterator(); it.hasNext();) {
      LogKillsDeathsJob.KillsDeathsRecord kdr = it.next();
      if (kdr.getPlayer().equals(player)) {
        kills += kdr.getKills();
        deaths += kdr.getDeaths();
        it.remove();
      }
    }
    LogKillsDeathsJob.KillsDeathsRecord kdr = new LogKillsDeathsJob.KillsDeathsRecord(player, kills, deaths);
    this.killsDeathsTracker.add(kdr);
  }

  public List<LogKillsDeathsJob.KillsDeathsRecord> getKillsDeathsTracker() {
    return killsDeathsTracker;
  }

  /**
   * Send a message to all teams.
   * @param message Message or key to translate.
   */
  public void broadcast(String message) {
    for (Team team : this.teams) {
      team.teamcast(message);
    }
  }

  /**
   * Send a message to all teams.
   * @param message Message or key to translate.
   * @param args Arguments for the formatter.
   */
  public void broadcast(String message, Object... args) {
    for (Team team : this.teams) {
      team.teamcast(message, args);
    }
  }

  /**
   * Get a list of all players in the warzone. The list is immutable. If you
   * need to modify the player list, you must use the per-team lists
   *
   * @return list containing all team players.
   */
  public List<Player> getPlayers() {
    List<Player> players = new ArrayList<Player>();
    for (Team team : this.teams) {
      players.addAll(team.getPlayers());
    }
    return players;
  }

  /**
   * Get the amount of players in all teams in this warzone.
   *
   * @return total player count
   */
  public int getPlayerCount() {
    int count = 0;
    for (Team team : this.teams) {
      count += team.getPlayers().size();
    }
    return count;
  }

  /**
   * Get the amount of players in all teams in this warzone. Same as
   * {@link #getPlayerCount()}, except only checks teams that the specified
   * player has permission to join.
   *
   * @param target
   *            Player to check for permissions.
   * @return total player count in teams the player has access to.
   */
  public int getPlayerCount(Permissible target) {
    int playerCount = 0;
    for (Team team : this.teams) {
      if (target.hasPermission(team.getTeamConfig().resolveString(
          TeamConfig.PERMISSION))) {
        playerCount += team.getPlayers().size();
      }
    }
    return playerCount;
  }

  /**
   * Get the total capacity of all teams in this zone. This should be
   * preferred over {@link TeamConfig#TEAMSIZE} as that can differ per team.
   *
   * @return capacity of all teams in this zone
   */
  public int getTotalCapacity() {
    int capacity = 0;
    for (Team team : this.teams) {
      capacity += team.getTeamConfig().resolveInt(TeamConfig.TEAMSIZE);
    }
    return capacity;
  }

  /**
   * Get the total capacity of all teams in this zone. Same as
   * {@link #getTotalCapacity()}, except only checks teams that the specified
   * player has permission to join.
   *
   * @param target
   *            Player to check for permissions.
   * @return capacity of teams the player has access to.
   */
  public int getTotalCapacity(Permissible target) {
    int capacity = 0;
    for (Team team : this.teams) {
      if (target.hasPermission(team.getTeamConfig().resolveString(
          TeamConfig.PERMISSION))) {
        capacity += team.getTeamConfig()
            .resolveInt(TeamConfig.TEAMSIZE);
      }
    }
    return capacity;
  }

  /**
   * Check if all teams are full.
   *
   * @return true if all teams are full, false otherwise.
   */
  public boolean isFull() {
    return this.getPlayerCount() == this.getTotalCapacity();
  }

  /**
   * Check if all teams are full. Same as {@link #isFull()}, except only
   * checks teams that the specified player has permission to join.
   *
   * @param target
   *            Player to check for permissions.
   * @return true if all teams are full, false otherwise.
   */
  public boolean isFull(Permissible target) {
    return this.getPlayerCount(target) == this.getTotalCapacity(target);
  }

  public void dropAllStolenObjects(Player player, boolean quiet) {
    if (this.isFlagThief(player.getName())) {
      Team victimTeam = this.getVictimTeamForFlagThief(player.getName());

      this.removeFlagThief(player.getName());

      // Bring back flag of victim team
      victimTeam.getFlagVolume().resetBlocks();
      victimTeam.initializeTeamFlag();

      if (!quiet) {
        this.broadcast("drop.flag.broadcast", player.getName(), victimTeam.getKind().getColor() + victimTeam.getName() + ChatColor.WHITE);
      }
    } else if (this.isCakeThief(player.getName())) {
      Cake cake = this.getCakeForThief(player.getName());

      this.removeCakeThief(player.getName());

      // Bring back cake
      cake.getVolume().resetBlocks();
      cake.addCakeBlocks();

      if (!quiet) {
        this.broadcast("drop.cake.broadcast", player.getName(), ChatColor.GREEN + cake.getName() + ChatColor.WHITE);
      }
    } else if (this.isBombThief(player.getName())) {
      Bomb bomb = this.getBombForThief(player.getName());

      this.removeBombThief(player.getName());

      // Bring back bomb
      bomb.getVolume().resetBlocks();
      bomb.addBombBlocks();

      if (!quiet) {
        this.broadcast("drop.bomb.broadcast", player.getName(), ChatColor.GREEN + bomb.getName() + ChatColor.WHITE);
      }
    }
  }

  /**
   * Get the proper ending teleport location for players leaving the warzone.
   * <p>
   * Specifically, it gets teleports in this order:
   * <ul>
   * <li>Rally point (if scorecap)
   * <li>Warhub (if autojoin)
   * <li>Lobby
   * </ul>
   * </p>
   * @param reason Reason for leaving zone
   * @return
   */
  public Location getEndTeleport(LeaveCause reason) {
    if (reason.useRallyPoint() && this.getRallyPoint() != null) {
      return this.getRallyPoint();
    }
    if (this.getWarzoneConfig().getBoolean(WarzoneConfig.AUTOJOIN)
        && War.war.getWarHub() != null) {
      return War.war.getWarHub().getLocation();
    }
    return this.getTeleport();
  }

  public Volume loadStructure(String volName, World world) throws SQLException {
    return loadStructure(volName, world, ZoneVolumeMapper.getZoneConnection(volume, name, world));
  }

  public Volume loadStructure(String volName, Connection zoneConnection) throws SQLException {
    return loadStructure(volName, world, zoneConnection);
  }

  public Volume loadStructure(String volName, World world, Connection zoneConnection) throws SQLException {
    Volume volume = new Volume(volName, world);
    if (!containsTable(String.format("structure_%d_corners", volName.hashCode() & Integer.MAX_VALUE), zoneConnection)) {
      volume = VolumeMapper.loadVolume(volName, name, world);
      ZoneVolumeMapper.saveStructure(volume, zoneConnection);
      War.war.getLogger().log(Level.INFO, "Stuffed structure {0} into database for warzone {1}", new Object[] {volName, name});
      return volume;
    }
    ZoneVolumeMapper.loadStructure(volume, zoneConnection);
    return volume;
  }

  private boolean containsTable(String table, Connection connection) throws SQLException {
    PreparedStatement stmt = connection.prepareStatement("SELECT COUNT(*) AS ct FROM sqlite_master WHERE type = ? AND name = ?");
    stmt.setString(1, "table");
    stmt.setString(2, table);
    ResultSet resultSet = stmt.executeQuery();
    try {
      return resultSet.next() && resultSet.getInt("ct") > 0;
    } finally {
      resultSet.close();
      stmt.close();
    }
  }

  /**
   * Check if a player has stolen from a warzone flag, bomb, or cake.
   * @param suspect Player to check.
   * @return true if suspect has stolen a structure.
   */
  public boolean isThief(String suspect) {
    return this.isFlagThief(suspect) || this.isBombThief(suspect) || this.isCakeThief(suspect);
  }
}
TOP

Related Classes of com.tommytony.war.Warzone

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.